2 /* Copyright (C) 1989-2014 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
24 void print_object_list(object *);
26 line_type::line_type()
27 : type(solid), thickness(1.0)
31 output::output() : args(0), desired_height(0.0), desired_width(0.0)
40 void output::set_desired_width_height(double wid, double ht)
46 void output::set_args(const char *s)
49 if (s == 0 || *s == '\0')
55 int output::supports_filled_polygons()
60 void output::begin_block(const position &, const position &)
64 void output::end_block()
68 double output::compute_scale(double sc, const position &ll, const position &ur)
70 distance dim = ur - ll;
71 if (desired_width != 0.0 || desired_height != 0.0) {
73 if (desired_width != 0.0) {
75 error("width specified for picture with zero width");
77 sc = dim.x/desired_width;
79 if (desired_height != 0.0) {
81 error("height specified for picture with zero height");
83 double tem = dim.y/desired_height;
88 return sc == 0.0 ? 1.0 : sc;
93 distance sdim = dim/sc;
94 double max_width = 0.0;
95 lookup_variable("maxpswid", &max_width);
96 double max_height = 0.0;
97 lookup_variable("maxpsht", &max_height);
98 if ((max_width > 0.0 && sdim.x > max_width)
99 || (max_height > 0.0 && sdim.y > max_height)) {
100 double xscale = dim.x/max_width;
101 double yscale = dim.y/max_height;
102 return xscale > yscale ? xscale : yscale;
109 position::position(const place &pl)
112 // Use two statements to work around bug in SGI C++.
113 object *tem = pl.obj;
114 *this = tem->origin();
122 position::position() : x(0.0), y(0.0)
126 position::position(double a, double b) : x(a), y(b)
131 int operator==(const position &a, const position &b)
133 return a.x == b.x && a.y == b.y;
136 int operator!=(const position &a, const position &b)
138 return a.x != b.x || a.y != b.y;
141 position &position::operator+=(const position &a)
148 position &position::operator-=(const position &a)
155 position &position::operator*=(double a)
162 position &position::operator/=(double a)
169 position operator-(const position &a)
171 return position(-a.x, -a.y);
174 position operator+(const position &a, const position &b)
176 return position(a.x + b.x, a.y + b.y);
179 position operator-(const position &a, const position &b)
181 return position(a.x - b.x, a.y - b.y);
184 position operator/(const position &a, double n)
186 return position(a.x/n, a.y/n);
189 position operator*(const position &a, double n)
191 return position(a.x*n, a.y*n);
196 double operator*(const position &a, const position &b)
198 return a.x*b.x + a.y*b.y;
201 double hypot(const position &a)
203 return groff_hypot(a.x, a.y);
206 struct arrow_head_type {
212 void draw_arrow(const position &pos, const distance &dir,
213 const arrow_head_type &aht, const line_type <,
214 char *outline_color_for_fill)
216 double hyp = hypot(dir);
218 error("cannot draw arrow on object with zero length");
221 position base = -dir;
222 base *= aht.height/hyp;
223 position n(dir.y, -dir.x);
224 n *= aht.width/(hyp*2.0);
226 slt.type = line_type::solid;
227 if (aht.solid && out->supports_filled_polygons()) {
230 v[1] = pos + base + n;
231 v[2] = pos + base - n;
232 // fill with outline color
233 out->set_color(outline_color_for_fill, outline_color_for_fill);
234 // make stroke thin to avoid arrow sticking
236 out->polygon(v, 3, slt, 1);
239 // use two line segments to avoid arrow sticking
240 out->line(pos + base - n, &pos, 1, slt);
241 out->line(pos + base + n, &pos, 1, slt);
245 object::object() : prev(0), next(0)
253 void object::move_by(const position &)
261 void object::print_text()
270 struct bounding_box {
276 void encompass(const position &);
279 bounding_box::bounding_box()
284 void bounding_box::encompass(const position &pos)
303 void object::update_bounding_box(bounding_box *)
307 position object::origin()
309 return position(0.0,0.0);
312 position object::north()
317 position object::south()
322 position object::east()
327 position object::west()
332 position object::north_east()
337 position object::north_west()
342 position object::south_east()
347 position object::south_west()
352 position object::start()
357 position object::end()
362 position object::center()
367 double object::width()
372 double object::radius()
377 double object::height()
382 place *object::find_label(const char *)
387 segment::segment(const position &a, int n, segment *p)
388 : is_absolute(n), pos(a), next(p)
392 text_item::text_item(char *t, const char *fn, int ln)
393 : next(0), text(t), filename(fn), lineno(ln)
395 adj.h = CENTER_ADJUST;
399 text_item::~text_item()
404 object_spec::object_spec(object_type t) : type(t)
409 segment_width = segment_height = 0.0;
410 segment_is_absolute = 0;
417 dir = RIGHT_DIRECTION;
420 object_spec::~object_spec()
423 while (segment_list != 0) {
424 segment *tem = segment_list;
425 segment_list = segment_list->next;
428 object *p = oblist.head;
435 text_item *tem = text;
444 class command_object : public object {
446 const char *filename;
449 command_object(char *, const char *, int);
451 object_type type() { return OTHER_OBJECT; }
455 command_object::command_object(char *p, const char *fn, int ln)
456 : s(p), filename(fn), lineno(ln)
460 command_object::~command_object()
465 void command_object::print()
467 out->command(s, filename, lineno);
470 object *make_command_object(char *s, const char *fn, int ln)
472 return new command_object(s, fn, ln);
475 class mark_object : public object {
481 object *make_mark_object()
483 return new mark_object();
486 mark_object::mark_object()
490 object_type mark_object::type()
495 object_list::object_list() : head(0), tail(0)
499 void object_list::append(object *obj)
502 obj->next = obj->prev = 0;
513 void object_list::wrap_up_block(object_list *ol)
516 for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev)
534 text_piece::text_piece()
535 : text(0), filename(0), lineno(-1)
537 adj.h = CENTER_ADJUST;
541 text_piece::~text_piece()
546 class graphic_object : public object {
557 object_type type() = 0;
559 void add_text(text_item *, int);
560 void set_dotted(double);
561 void set_dashed(double);
562 void set_thickness(double);
563 void set_invisible();
564 void set_outline_color(char *);
565 char *get_outline_color();
566 virtual void set_fill(double);
567 virtual void set_xslanted(double);
568 virtual void set_yslanted(double);
569 virtual void set_fill_color(char *);
572 graphic_object::graphic_object()
573 : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
577 void graphic_object::set_dotted(double wid)
579 lt.type = line_type::dotted;
583 void graphic_object::set_dashed(double wid)
585 lt.type = line_type::dashed;
589 void graphic_object::set_thickness(double th)
594 void graphic_object::set_fill(double)
598 void graphic_object::set_xslanted(double)
602 void graphic_object::set_yslanted(double)
606 void graphic_object::set_fill_color(char *c)
608 color_fill = strsave(c);
611 void graphic_object::set_outline_color(char *c)
613 outline_color = strsave(c);
616 char *graphic_object::get_outline_color()
618 return outline_color;
621 void graphic_object::set_invisible()
623 lt.type = line_type::invisible;
626 void graphic_object::add_text(text_item *t, int a)
631 for (p = t; p; p = p->next)
636 text = new text_piece[len];
637 for (p = t, len = 0; p; p = p->next, len++) {
638 text[len].text = p->text;
640 text[len].adj = p->adj;
641 text[len].filename = p->filename;
642 text[len].lineno = p->lineno;
648 void graphic_object::print_text()
652 position d(end() - start());
653 if (d.x != 0.0 || d.y != 0.0)
654 angle = atan2(d.y, d.x);
657 out->set_color(color_fill, get_outline_color());
658 out->text(center(), text, ntext, angle);
663 graphic_object::~graphic_object()
666 ad_delete(ntext) text;
669 class rectangle_object : public graphic_object {
674 rectangle_object(const position &);
675 double width() { return dim.x; }
676 double height() { return dim.y; }
677 position origin() { return cent; }
678 position center() { return cent; }
679 position north() { return position(cent.x, cent.y + dim.y/2.0); }
680 position south() { return position(cent.x, cent.y - dim.y/2.0); }
681 position east() { return position(cent.x + dim.x/2.0, cent.y); }
682 position west() { return position(cent.x - dim.x/2.0, cent.y); }
683 position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); }
684 position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); }
685 position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); }
686 position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); }
687 object_type type() = 0;
688 void update_bounding_box(bounding_box *);
689 void move_by(const position &);
692 rectangle_object::rectangle_object(const position &d)
697 void rectangle_object::update_bounding_box(bounding_box *p)
699 p->encompass(cent - dim/2.0);
700 p->encompass(cent + dim/2.0);
703 void rectangle_object::move_by(const position &a)
708 class closed_object : public rectangle_object {
710 closed_object(const position &);
711 object_type type() = 0;
712 void set_fill(double);
713 void set_xslanted(double);
714 void set_yslanted(double);
715 void set_fill_color(char *fill);
717 double fill; // < 0 if not filled
718 double xslanted; // !=0 if x is slanted
719 double yslanted; // !=0 if y is slanted
720 char *color_fill; // = 0 if not colored
723 closed_object::closed_object(const position &pos)
724 : rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
728 void closed_object::set_fill(double f)
734 /* accept positive and negative values */
735 void closed_object::set_xslanted(double s)
740 /* accept positive and negative values */
741 void closed_object::set_yslanted(double s)
747 void closed_object::set_fill_color(char *f)
749 color_fill = strsave(f);
752 class box_object : public closed_object {
756 box_object(const position &, double);
757 object_type type() { return BOX_OBJECT; }
759 position north_east();
760 position north_west();
761 position south_east();
762 position south_west();
765 box_object::box_object(const position &pos, double r)
766 : closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r)
770 const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
772 position box_object::north_east()
774 return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
775 cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
778 position box_object::north_west()
780 return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
781 cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
784 position box_object::south_east()
786 return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
787 cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
790 position box_object::south_west()
792 return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
793 cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
796 void box_object::print()
798 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
800 out->set_color(color_fill, graphic_object::get_outline_color());
802 distance dim2 = dim/2.0;
804 /* error("x slanted %1", xslanted); */
805 /* error("y slanted %1", yslanted); */
806 vec[0] = cent + position(dim2.x, -(dim2.y - yslanted)); /* lr */
807 vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted); /* ur */
808 vec[2] = cent + position(-(dim2.x - xslanted), dim2.y); /* ul */
809 vec[3] = cent + position(-(dim2.x), -dim2.y); /* ll */
810 out->polygon(vec, 4, lt, fill);
813 distance abs_dim(fabs(dim.x), fabs(dim.y));
814 out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill);
819 graphic_object *object_spec::make_box(position *curpos, direction *dirp)
821 static double last_box_height;
822 static double last_box_width;
823 static double last_box_radius;
824 static int have_last_box = 0;
825 if (!(flags & HAS_HEIGHT)) {
826 if ((flags & IS_SAME) && have_last_box)
827 height = last_box_height;
829 lookup_variable("boxht", &height);
831 if (!(flags & HAS_WIDTH)) {
832 if ((flags & IS_SAME) && have_last_box)
833 width = last_box_width;
835 lookup_variable("boxwid", &width);
837 if (!(flags & HAS_RADIUS)) {
838 if ((flags & IS_SAME) && have_last_box)
839 radius = last_box_radius;
841 lookup_variable("boxrad", &radius);
843 last_box_width = width;
844 last_box_height = height;
845 last_box_radius = radius;
847 radius = fabs(radius);
848 if (radius*2.0 > fabs(width))
849 radius = fabs(width/2.0);
850 if (radius*2.0 > fabs(height))
851 radius = fabs(height/2.0);
852 box_object *p = new box_object(position(width, height), radius);
853 if (!position_rectangle(p, curpos, dirp)) {
860 // return non-zero for success
862 int object_spec::position_rectangle(rectangle_object *p,
863 position *curpos, direction *dirp)
866 dir = *dirp; // ignore any direction in attribute list
870 motion.y = p->height()/2.0;
873 motion.y = -p->height()/2.0;
876 motion.x = -p->width()/2.0;
878 case RIGHT_DIRECTION:
879 motion.x = p->width()/2.0;
884 if (flags & HAS_AT) {
886 if (flags & HAS_WITH) {
890 if (!with->follow(here, &offset))
905 class block_object : public rectangle_object {
909 block_object(const position &, const object_list &ol, PTABLE(place) *t);
911 place *find_label(const char *);
913 void move_by(const position &);
917 block_object::block_object(const position &d, const object_list &ol,
919 : rectangle_object(d), oblist(ol), tbl(t)
923 block_object::~block_object()
926 object *p = oblist.head;
934 void block_object::print()
936 out->begin_block(south_west(), north_east());
937 print_object_list(oblist.head);
941 static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
943 // Adjust all the labels that aren't attached to objects.
944 PTABLE_ITERATOR(place) iter(tbl);
947 while (iter.next(&key, &pl))
948 if (key && csupper(key[0]) && pl->obj == 0) {
954 void block_object::move_by(const position &a)
957 for (object *p = oblist.head; p; p = p->next)
959 adjust_objectless_places(tbl, a);
963 place *block_object::find_label(const char *name)
965 return tbl->lookup(name);
968 object_type block_object::type()
973 graphic_object *object_spec::make_block(position *curpos, direction *dirp)
976 for (object *p = oblist.head; p; p = p->next)
977 p->update_bounding_box(&bb);
980 position m = -(bb.ll + bb.ur)/2.0;
981 for (object *p = oblist.head; p; p = p->next)
983 adjust_objectless_places(tbl, m);
986 if (flags & HAS_WIDTH)
988 if (flags & HAS_HEIGHT)
990 block_object *block = new block_object(dim, oblist, tbl);
991 if (!position_rectangle(block, curpos, dirp)) {
996 oblist.head = oblist.tail = 0;
1000 class text_object : public rectangle_object {
1002 text_object(const position &);
1003 object_type type() { return TEXT_OBJECT; }
1006 text_object::text_object(const position &d)
1007 : rectangle_object(d)
1011 graphic_object *object_spec::make_text(position *curpos, direction *dirp)
1013 if (!(flags & HAS_HEIGHT)) {
1014 lookup_variable("textht", &height);
1016 for (text_item *t = text; t; t = t->next)
1020 if (!(flags & HAS_WIDTH))
1021 lookup_variable("textwid", &width);
1022 text_object *p = new text_object(position(width, height));
1023 if (!position_rectangle(p, curpos, dirp)) {
1031 class ellipse_object : public closed_object {
1033 ellipse_object(const position &);
1034 position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1035 cent.y + dim.y/(M_SQRT2*2.0)); }
1036 position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1037 cent.y + dim.y/(M_SQRT2*2.0)); }
1038 position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1039 cent.y - dim.y/(M_SQRT2*2.0)); }
1040 position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1041 cent.y - dim.y/(M_SQRT2*2.0)); }
1042 double radius() { return dim.x/2.0; }
1043 object_type type() { return ELLIPSE_OBJECT; }
1047 ellipse_object::ellipse_object(const position &d)
1052 void ellipse_object::print()
1054 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1056 out->set_color(color_fill, graphic_object::get_outline_color());
1057 out->ellipse(cent, dim, lt, fill);
1061 graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
1063 static double last_ellipse_height;
1064 static double last_ellipse_width;
1065 static int have_last_ellipse = 0;
1066 if (!(flags & HAS_HEIGHT)) {
1067 if ((flags & IS_SAME) && have_last_ellipse)
1068 height = last_ellipse_height;
1070 lookup_variable("ellipseht", &height);
1072 if (!(flags & HAS_WIDTH)) {
1073 if ((flags & IS_SAME) && have_last_ellipse)
1074 width = last_ellipse_width;
1076 lookup_variable("ellipsewid", &width);
1078 last_ellipse_width = width;
1079 last_ellipse_height = height;
1080 have_last_ellipse = 1;
1081 ellipse_object *p = new ellipse_object(position(width, height));
1082 if (!position_rectangle(p, curpos, dirp)) {
1089 class circle_object : public ellipse_object {
1091 circle_object(double);
1092 object_type type() { return CIRCLE_OBJECT; }
1096 circle_object::circle_object(double diam)
1097 : ellipse_object(position(diam, diam))
1101 void circle_object::print()
1103 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1105 out->set_color(color_fill, graphic_object::get_outline_color());
1106 out->circle(cent, dim.x/2.0, lt, fill);
1110 graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
1112 static double last_circle_radius;
1113 static int have_last_circle = 0;
1114 if (!(flags & HAS_RADIUS)) {
1115 if ((flags & IS_SAME) && have_last_circle)
1116 radius = last_circle_radius;
1118 lookup_variable("circlerad", &radius);
1120 last_circle_radius = radius;
1121 have_last_circle = 1;
1122 circle_object *p = new circle_object(radius*2.0);
1123 if (!position_rectangle(p, curpos, dirp)) {
1130 class move_object : public graphic_object {
1134 move_object(const position &s, const position &e);
1135 position origin() { return en; }
1136 object_type type() { return MOVE_OBJECT; }
1137 void update_bounding_box(bounding_box *);
1138 void move_by(const position &);
1141 move_object::move_object(const position &s, const position &e)
1146 void move_object::update_bounding_box(bounding_box *p)
1152 void move_object::move_by(const position &a)
1158 graphic_object *object_spec::make_move(position *curpos, direction *dirp)
1160 static position last_move;
1161 static int have_last_move = 0;
1163 // No need to look at at since `at' attribute sets `from' attribute.
1164 position startpos = (flags & HAS_FROM) ? from : *curpos;
1165 if (!(flags & HAS_SEGMENT)) {
1166 if ((flags & IS_SAME) && have_last_move)
1167 segment_pos = last_move;
1171 segment_pos.y = segment_height;
1173 case DOWN_DIRECTION:
1174 segment_pos.y = -segment_height;
1176 case LEFT_DIRECTION:
1177 segment_pos.x = -segment_width;
1179 case RIGHT_DIRECTION:
1180 segment_pos.x = segment_width;
1187 segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1188 // Reverse the segment_list so that it's in forward order.
1189 segment *old = segment_list;
1192 segment *tem = old->next;
1193 old->next = segment_list;
1197 // Compute the end position.
1198 position endpos = startpos;
1199 for (segment *s = segment_list; s; s = s->next)
1205 last_move = endpos - startpos;
1206 move_object *p = new move_object(startpos, endpos);
1211 class linear_object : public graphic_object {
1213 char arrow_at_start;
1215 arrow_head_type aht;
1219 linear_object(const position &s, const position &e);
1220 position start() { return strt; }
1221 position end() { return en; }
1222 void move_by(const position &);
1223 void update_bounding_box(bounding_box *) = 0;
1224 object_type type() = 0;
1225 void add_arrows(int at_start, int at_end, const arrow_head_type &);
1228 class line_object : public linear_object {
1233 line_object(const position &s, const position &e, position *, int);
1235 position origin() { return strt; }
1236 position center() { return (strt + en)/2.0; }
1237 position north() { return (en.y - strt.y) > 0 ? en : strt; }
1238 position south() { return (en.y - strt.y) < 0 ? en : strt; }
1239 position east() { return (en.x - strt.x) > 0 ? en : strt; }
1240 position west() { return (en.x - strt.x) < 0 ? en : strt; }
1241 object_type type() { return LINE_OBJECT; }
1242 void update_bounding_box(bounding_box *);
1244 void move_by(const position &);
1247 class arrow_object : public line_object {
1249 arrow_object(const position &, const position &, position *, int);
1250 object_type type() { return ARROW_OBJECT; }
1253 class spline_object : public line_object {
1255 spline_object(const position &, const position &, position *, int);
1256 object_type type() { return SPLINE_OBJECT; }
1258 void update_bounding_box(bounding_box *);
1261 linear_object::linear_object(const position &s, const position &e)
1262 : arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
1266 void linear_object::move_by(const position &a)
1272 void linear_object::add_arrows(int at_start, int at_end,
1273 const arrow_head_type &a)
1275 arrow_at_start = at_start;
1276 arrow_at_end = at_end;
1280 line_object::line_object(const position &s, const position &e,
1282 : linear_object(s, e), v(p), n(i)
1286 void line_object::print()
1288 if (lt.type == line_type::invisible)
1290 out->set_color(0, graphic_object::get_outline_color());
1291 // shorten line length to avoid arrow sticking.
1293 if (arrow_at_start) {
1294 position base = v[0] - strt;
1295 double hyp = hypot(base);
1297 error("cannot draw arrow on object with zero length");
1300 if (aht.solid && out->supports_filled_polygons()) {
1301 base *= aht.height / hyp;
1302 draw_arrow(strt, strt - v[0], aht, lt,
1303 graphic_object::get_outline_color());
1306 base *= fabs(lt.thickness) / hyp / 72 / 4;
1308 draw_arrow(sp, sp - v[0], aht, lt,
1309 graphic_object::get_outline_color());
1313 position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1314 double hyp = hypot(base);
1316 error("cannot draw arrow on object with zero length");
1319 if (aht.solid && out->supports_filled_polygons()) {
1320 base *= aht.height / hyp;
1321 draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1322 graphic_object::get_outline_color());
1325 base *= fabs(lt.thickness) / hyp / 72 / 4;
1327 draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1328 graphic_object::get_outline_color());
1331 out->line(sp, v, n, lt);
1335 void line_object::update_bounding_box(bounding_box *p)
1338 for (int i = 0; i < n; i++)
1342 void line_object::move_by(const position &pos)
1344 linear_object::move_by(pos);
1345 for (int i = 0; i < n; i++)
1349 void spline_object::update_bounding_box(bounding_box *p)
1361 [ the points for the Bezier cubic ]
1369 (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4
1370 [ the equation for the Bezier cubic ]
1372 = .125*q1 + .75*q2 + .125*q3
1375 for (int i = 1; i < n; i++)
1376 p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125);
1379 arrow_object::arrow_object(const position &s, const position &e,
1381 : line_object(s, e, p, i)
1385 spline_object::spline_object(const position &s, const position &e,
1387 : line_object(s, e, p, i)
1391 void spline_object::print()
1393 if (lt.type == line_type::invisible)
1395 out->set_color(0, graphic_object::get_outline_color());
1396 // shorten line length for spline to avoid arrow sticking
1398 if (arrow_at_start) {
1399 position base = v[0] - strt;
1400 double hyp = hypot(base);
1402 error("cannot draw arrow on object with zero length");
1405 if (aht.solid && out->supports_filled_polygons()) {
1406 base *= aht.height / hyp;
1407 draw_arrow(strt, strt - v[0], aht, lt,
1408 graphic_object::get_outline_color());
1409 sp = strt + base*0.1; // to reserve spline shape
1411 base *= fabs(lt.thickness) / hyp / 72 / 4;
1413 draw_arrow(sp, sp - v[0], aht, lt,
1414 graphic_object::get_outline_color());
1418 position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1419 double hyp = hypot(base);
1421 error("cannot draw arrow on object with zero length");
1424 if (aht.solid && out->supports_filled_polygons()) {
1425 base *= aht.height / hyp;
1426 draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1427 graphic_object::get_outline_color());
1428 v[n-1] = en - base*0.1; // to reserve spline shape
1430 base *= fabs(lt.thickness) / hyp / 72 / 4;
1432 draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1433 graphic_object::get_outline_color());
1436 out->spline(sp, v, n, lt);
1440 line_object::~line_object()
1445 linear_object *object_spec::make_line(position *curpos, direction *dirp)
1447 static position last_line;
1448 static int have_last_line = 0;
1450 // We handle `at' only in conjunction with `with', otherwise it is
1451 // the same as the `from' attribute.
1453 if ((flags & HAS_AT) && (flags & HAS_WITH))
1454 // handled later -- we need the end position
1456 else if (flags & HAS_FROM)
1460 if (!(flags & HAS_SEGMENT)) {
1461 if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
1463 segment_pos = last_line;
1467 segment_pos.y = segment_height;
1469 case DOWN_DIRECTION:
1470 segment_pos.y = -segment_height;
1472 case LEFT_DIRECTION:
1473 segment_pos.x = -segment_width;
1475 case RIGHT_DIRECTION:
1476 segment_pos.x = segment_width;
1482 segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1483 // reverse the segment_list so that it's in forward order
1484 segment *old = segment_list;
1487 segment *tem = old->next;
1488 old->next = segment_list;
1492 // Absolutise all movements
1493 position endpos = startpos;
1496 for (s = segment_list; s; s = s->next, nsegments++)
1502 s->is_absolute = 1; // to avoid confusion
1504 if ((flags & HAS_AT) && (flags & HAS_WITH)) {
1505 // `tmpobj' works for arrows and splines too -- we only need positions
1506 line_object tmpobj(startpos, endpos, 0, 0);
1511 if (!with->follow(here, &offset))
1514 for (s = segment_list; s; s = s->next)
1521 position *v = new position[nsegments];
1523 for (s = segment_list; s; s = s->next, i++)
1525 if (flags & IS_DEFAULT_CHOPPED) {
1526 lookup_variable("circlerad", &start_chop);
1527 end_chop = start_chop;
1528 flags |= IS_CHOPPED;
1530 if (flags & IS_CHOPPED) {
1531 position start_chop_vec, end_chop_vec;
1532 if (start_chop != 0.0) {
1533 start_chop_vec = v[0] - startpos;
1534 start_chop_vec *= start_chop / hypot(start_chop_vec);
1536 if (end_chop != 0.0) {
1537 end_chop_vec = (v[nsegments - 1]
1538 - (nsegments > 1 ? v[nsegments - 2] : startpos));
1539 end_chop_vec *= end_chop / hypot(end_chop_vec);
1541 startpos += start_chop_vec;
1542 v[nsegments - 1] -= end_chop_vec;
1543 endpos -= end_chop_vec;
1547 p = new spline_object(startpos, endpos, v, nsegments);
1550 p = new arrow_object(startpos, endpos, v, nsegments);
1553 p = new line_object(startpos, endpos, v, nsegments);
1559 last_line = endpos - startpos;
1564 class arc_object : public linear_object {
1569 arc_object(int, const position &, const position &, const position &);
1570 position origin() { return cent; }
1571 position center() { return cent; }
1572 double radius() { return rad; }
1577 position north_east();
1578 position north_west();
1579 position south_east();
1580 position south_west();
1581 void update_bounding_box(bounding_box *);
1582 object_type type() { return ARC_OBJECT; }
1584 void move_by(const position &pos);
1587 arc_object::arc_object(int cw, const position &s, const position &e,
1589 : linear_object(s, e), clockwise(cw), cent(c)
1594 void arc_object::move_by(const position &pos)
1596 linear_object::move_by(pos);
1600 // we get arc corners from the corresponding circle
1602 position arc_object::north()
1604 position result(cent);
1609 position arc_object::south()
1611 position result(cent);
1616 position arc_object::east()
1618 position result(cent);
1623 position arc_object::west()
1625 position result(cent);
1630 position arc_object::north_east()
1632 position result(cent);
1633 result.x += rad/M_SQRT2;
1634 result.y += rad/M_SQRT2;
1638 position arc_object::north_west()
1640 position result(cent);
1641 result.x -= rad/M_SQRT2;
1642 result.y += rad/M_SQRT2;
1646 position arc_object::south_east()
1648 position result(cent);
1649 result.x += rad/M_SQRT2;
1650 result.y -= rad/M_SQRT2;
1654 position arc_object::south_west()
1656 position result(cent);
1657 result.x -= rad/M_SQRT2;
1658 result.y -= rad/M_SQRT2;
1663 void arc_object::print()
1665 if (lt.type == line_type::invisible)
1667 out->set_color(0, graphic_object::get_outline_color());
1668 // handle arrow direction; make shorter line for arc
1677 if (arrow_at_start) {
1678 double theta = aht.height / rad;
1682 b = position(b.x*cos(theta) - b.y*sin(theta),
1683 b.x*sin(theta) + b.y*cos(theta)) + cent;
1688 if (aht.solid && out->supports_filled_polygons()) {
1689 draw_arrow(strt, strt - b, aht, lt,
1690 graphic_object::get_outline_color());
1693 theta = fabs(lt.thickness) / 72 / 4 / rad;
1697 b = position(b.x*cos(theta) - b.y*sin(theta),
1698 b.x*sin(theta) + b.y*cos(theta)) + cent;
1699 draw_arrow(b, b - v, aht, lt,
1700 graphic_object::get_outline_color());
1701 out->line(b, &v, 1, lt);
1705 double theta = aht.height / rad;
1709 b = position(b.x*cos(theta) - b.y*sin(theta),
1710 b.x*sin(theta) + b.y*cos(theta)) + cent;
1715 if (aht.solid && out->supports_filled_polygons()) {
1716 draw_arrow(en, en - b, aht, lt,
1717 graphic_object::get_outline_color());
1720 theta = fabs(lt.thickness) / 72 / 4 / rad;
1724 b = position(b.x*cos(theta) - b.y*sin(theta),
1725 b.x*sin(theta) + b.y*cos(theta)) + cent;
1726 draw_arrow(b, b - v, aht, lt,
1727 graphic_object::get_outline_color());
1728 out->line(b, &v, 1, lt);
1731 out->arc(sp, cent, ep, lt);
1735 inline double max(double a, double b)
1737 return a > b ? a : b;
1740 void arc_object::update_bounding_box(bounding_box *p)
1744 position start_offset = strt - cent;
1745 if (start_offset.x == 0.0 && start_offset.y == 0.0)
1747 position end_offset = en - cent;
1748 if (end_offset.x == 0.0 && end_offset.y == 0.0)
1750 double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0);
1751 double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0);
1753 double temp = start_quad;
1754 start_quad = end_quad;
1757 if (start_quad < 0.0)
1759 while (end_quad <= start_quad)
1761 double r = max(hypot(start_offset), hypot(end_offset));
1762 for (int q = int(start_quad) + 1; q < end_quad; q++) {
1778 p->encompass(cent + offset);
1782 // We ignore the with attribute. The at attribute always refers to the center.
1784 linear_object *object_spec::make_arc(position *curpos, direction *dirp)
1787 int cw = (flags & IS_CLOCKWISE) != 0;
1788 // compute the start
1790 if (flags & HAS_FROM)
1794 if (!(flags & HAS_RADIUS))
1795 lookup_variable("arcrad", &radius);
1801 position m(radius, radius);
1802 // Adjust the signs.
1804 if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1806 if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
1808 *dirp = direction((dir + 3) % 4);
1811 if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
1813 if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1815 *dirp = direction((dir + 1) % 4);
1817 endpos = startpos + m;
1819 // compute the center
1823 else if (startpos == endpos)
1824 centerpos = startpos;
1826 position h = (endpos - startpos)/2.0;
1827 double d = hypot(h);
1830 // make the radius big enough
1833 double alpha = acos(d/radius);
1834 double theta = atan2(h.y, h.x);
1839 centerpos = position(cos(theta), sin(theta))*radius + startpos;
1841 arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
1846 graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
1849 if (type == ARC_OBJECT)
1850 obj = make_arc(curpos, dirp);
1852 obj = make_line(curpos, dirp);
1853 if (type == ARROW_OBJECT
1854 && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0)
1855 flags |= HAS_RIGHT_ARROW_HEAD;
1856 if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) {
1858 int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0;
1859 int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0;
1860 if (flags & HAS_HEIGHT)
1863 lookup_variable("arrowht", &a.height);
1864 if (flags & HAS_WIDTH)
1867 lookup_variable("arrowwid", &a.width);
1869 lookup_variable("arrowhead", &solid);
1870 a.solid = solid != 0.0;
1871 obj->add_arrows(at_start, at_end, a);
1876 object *object_spec::make_object(position *curpos, direction *dirp)
1878 graphic_object *obj = 0;
1881 obj = make_block(curpos, dirp);
1884 obj = make_box(curpos, dirp);
1887 obj = make_text(curpos, dirp);
1889 case ELLIPSE_OBJECT:
1890 obj = make_ellipse(curpos, dirp);
1893 obj = make_circle(curpos, dirp);
1896 obj = make_move(curpos, dirp);
1902 obj = make_linear(curpos, dirp);
1911 if (flags & IS_INVISIBLE)
1912 obj->set_invisible();
1914 obj->add_text(text, (flags & IS_ALIGNED) != 0);
1915 if (flags & IS_DOTTED)
1916 obj->set_dotted(dash_width);
1917 else if (flags & IS_DASHED)
1918 obj->set_dashed(dash_width);
1920 if (flags & HAS_THICKNESS)
1923 lookup_variable("linethick", &th);
1924 obj->set_thickness(th);
1925 if (flags & IS_OUTLINED)
1926 obj->set_outline_color(outlined);
1927 if (flags & IS_XSLANTED)
1928 obj->set_xslanted(xslanted);
1929 if (flags & IS_YSLANTED)
1930 obj->set_yslanted(yslanted);
1931 if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) {
1932 if (flags & IS_SHADED)
1933 obj->set_fill_color(shaded);
1935 if (flags & IS_DEFAULT_FILLED)
1936 lookup_variable("fillval", &fill);
1938 error("bad fill value %1", fill);
1940 obj->set_fill(fill);
1947 struct string_list {
1950 string_list(char *);
1954 string_list::string_list(char *s)
1959 string_list::~string_list()
1964 /* A path is used to hold the argument to the `with' attribute. For
1965 example, `.nw' or `.A.s' or `.A'. The major operation on a path is to
1966 take a place and follow the path through the place to place within the
1967 place. Note that `.A.B.C.sw' will work.
1969 For compatibility with DWB pic, `with' accepts positions also (this
1970 is incorrectly documented in CSTR 116). */
1972 path::path(corner c)
1973 : crn(c), label_list(0), ypath(0), is_position(0)
1977 path::path(position p)
1978 : crn(0), label_list(0), ypath(0), is_position(1)
1984 path::path(char *l, corner c)
1985 : crn(c), ypath(0), is_position(0)
1987 label_list = new string_list(l);
1992 while (label_list) {
1993 string_list *tem = label_list;
1994 label_list = label_list->next;
2000 void path::append(corner c)
2006 void path::append(char *s)
2009 for (p = &label_list; *p; p = &(*p)->next)
2011 *p = new string_list(s);
2014 void path::set_ypath(path *p)
2019 // return non-zero for success
2021 int path::follow(const place &pl, place *result) const
2029 const place *p = &pl;
2030 for (string_list *lb = label_list; lb; lb = lb->next)
2031 if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) {
2032 lex_error("object does not contain a place `%1'", lb->str);
2035 if (crn == 0 || p->obj == 0)
2038 position ps = ((p->obj)->*(crn))();
2045 if (!ypath->follow(pl, &tem))
2048 if (result->obj != tem.obj)
2054 void print_object_list(object *p)
2056 for (; p; p = p->next) {
2062 void print_picture(object *obj)
2065 for (object *p = obj; p; p = p->next)
2066 p->update_bounding_box(&bb);
2068 lookup_variable("scale", &scale);
2069 out->start_picture(scale, bb.ll, bb.ur);
2070 print_object_list(obj);
2071 out->finish_picture();