2 /* Copyright (C) 1989-2018 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/>. */
26 void print_object_list(object *);
28 line_type::line_type()
29 : type(solid), thickness(1.0)
33 output::output() : args(0), desired_height(0.0), desired_width(0.0)
42 void output::set_desired_width_height(double wid, double ht)
48 void output::set_args(const char *s)
51 if (s == 0 || *s == '\0')
57 int output::supports_filled_polygons()
62 void output::begin_block(const position &, const position &)
66 void output::end_block()
70 double output::compute_scale(double sc, const position &ll, const position &ur)
72 distance dim = ur - ll;
73 if (desired_width != 0.0 || desired_height != 0.0) {
75 if (desired_width != 0.0) {
77 error("width specified for picture with zero width");
79 sc = dim.x/desired_width;
81 if (desired_height != 0.0) {
83 error("height specified for picture with zero height");
85 double tem = dim.y/desired_height;
90 return sc == 0.0 ? 1.0 : sc;
95 distance sdim = dim/sc;
96 double max_width = 0.0;
97 lookup_variable("maxpswid", &max_width);
98 double max_height = 0.0;
99 lookup_variable("maxpsht", &max_height);
100 if ((max_width > 0.0 && sdim.x > max_width)
101 || (max_height > 0.0 && sdim.y > max_height)) {
102 double xscale = dim.x/max_width;
103 double yscale = dim.y/max_height;
104 return xscale > yscale ? xscale : yscale;
111 position::position(const place &pl)
114 // Use two statements to work around bug in SGI C++.
115 object *tem = pl.obj;
116 *this = tem->origin();
124 position::position() : x(0.0), y(0.0)
128 position::position(double a, double b) : x(a), y(b)
133 int operator==(const position &a, const position &b)
135 return a.x == b.x && a.y == b.y;
138 int operator!=(const position &a, const position &b)
140 return a.x != b.x || a.y != b.y;
143 position &position::operator+=(const position &a)
150 position &position::operator-=(const position &a)
157 position &position::operator*=(double a)
164 position &position::operator/=(double a)
171 position operator-(const position &a)
173 return position(-a.x, -a.y);
176 position operator+(const position &a, const position &b)
178 return position(a.x + b.x, a.y + b.y);
181 position operator-(const position &a, const position &b)
183 return position(a.x - b.x, a.y - b.y);
186 position operator/(const position &a, double n)
188 return position(a.x/n, a.y/n);
191 position operator*(const position &a, double n)
193 return position(a.x*n, a.y*n);
198 double operator*(const position &a, const position &b)
200 return a.x*b.x + a.y*b.y;
203 double hypot(const position &a)
205 return groff_hypot(a.x, a.y);
208 struct arrow_head_type {
214 void draw_arrow(const position &pos, const distance &dir,
215 const arrow_head_type &aht, const line_type <,
216 char *outline_color_for_fill)
218 double hyp = hypot(dir);
220 error("cannot draw arrow on object with zero length");
223 position base = -dir;
224 base *= aht.height/hyp;
225 position n(dir.y, -dir.x);
226 n *= aht.width/(hyp*2.0);
228 slt.type = line_type::solid;
229 if (aht.solid && out->supports_filled_polygons()) {
232 v[1] = pos + base + n;
233 v[2] = pos + base - n;
234 // fill with outline color
235 out->set_color(outline_color_for_fill, outline_color_for_fill);
236 // make stroke thin to avoid arrow sticking
238 out->polygon(v, 3, slt, 1);
241 // use two line segments to avoid arrow sticking
242 out->line(pos + base - n, &pos, 1, slt);
243 out->line(pos + base + n, &pos, 1, slt);
247 object::object() : prev(0), next(0)
255 void object::move_by(const position &)
263 void object::print_text()
272 struct bounding_box {
278 void encompass(const position &);
281 bounding_box::bounding_box()
286 void bounding_box::encompass(const position &pos)
305 void object::update_bounding_box(bounding_box *)
309 position object::origin()
311 return position(0.0,0.0);
314 position object::north()
319 position object::south()
324 position object::east()
329 position object::west()
334 position object::north_east()
339 position object::north_west()
344 position object::south_east()
349 position object::south_west()
354 position object::start()
359 position object::end()
364 position object::center()
369 double object::width()
374 double object::radius()
379 double object::height()
384 place *object::find_label(const char *)
389 segment::segment(const position &a, int n, segment *p)
390 : is_absolute(n), pos(a), next(p)
394 text_item::text_item(char *t, const char *fn, int ln)
395 : next(0), text(t), filename(fn), lineno(ln)
397 adj.h = CENTER_ADJUST;
401 text_item::~text_item()
406 object_spec::object_spec(object_type t) : type(t)
411 segment_width = segment_height = 0.0;
412 segment_is_absolute = 0;
419 dir = RIGHT_DIRECTION;
422 object_spec::~object_spec()
425 while (segment_list != 0) {
426 segment *tem = segment_list;
427 segment_list = segment_list->next;
430 object *p = oblist.head;
437 text_item *tem = text;
446 class command_object : public object {
448 const char *filename;
451 command_object(char *, const char *, int);
453 object_type type() { return OTHER_OBJECT; }
457 command_object::command_object(char *p, const char *fn, int ln)
458 : s(p), filename(fn), lineno(ln)
462 command_object::~command_object()
467 void command_object::print()
469 out->command(s, filename, lineno);
472 object *make_command_object(char *s, const char *fn, int ln)
474 return new command_object(s, fn, ln);
477 class mark_object : public object {
483 object *make_mark_object()
485 return new mark_object();
488 mark_object::mark_object()
492 object_type mark_object::type()
497 object_list::object_list() : head(0), tail(0)
501 void object_list::append(object *obj)
504 obj->next = obj->prev = 0;
515 void object_list::wrap_up_block(object_list *ol)
518 for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev)
536 text_piece::text_piece()
537 : text(0), filename(0), lineno(-1)
539 adj.h = CENTER_ADJUST;
543 text_piece::~text_piece()
548 class graphic_object : public object {
559 object_type type() = 0;
561 void add_text(text_item *, int);
562 void set_dotted(double);
563 void set_dashed(double);
564 void set_thickness(double);
565 void set_invisible();
566 void set_outline_color(char *);
567 char *get_outline_color();
568 virtual void set_fill(double);
569 virtual void set_xslanted(double);
570 virtual void set_yslanted(double);
571 virtual void set_fill_color(char *);
574 graphic_object::graphic_object()
575 : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
579 void graphic_object::set_dotted(double wid)
581 lt.type = line_type::dotted;
585 void graphic_object::set_dashed(double wid)
587 lt.type = line_type::dashed;
591 void graphic_object::set_thickness(double th)
596 void graphic_object::set_fill(double)
600 void graphic_object::set_xslanted(double)
604 void graphic_object::set_yslanted(double)
608 void graphic_object::set_fill_color(char *c)
610 color_fill = strsave(c);
613 void graphic_object::set_outline_color(char *c)
615 outline_color = strsave(c);
618 char *graphic_object::get_outline_color()
620 return outline_color;
623 void graphic_object::set_invisible()
625 lt.type = line_type::invisible;
628 void graphic_object::add_text(text_item *t, int a)
633 for (p = t; p; p = p->next)
638 text = new text_piece[len];
639 for (p = t, len = 0; p; p = p->next, len++) {
640 text[len].text = p->text;
642 text[len].adj = p->adj;
643 text[len].filename = p->filename;
644 text[len].lineno = p->lineno;
650 void graphic_object::print_text()
654 position d(end() - start());
655 if (d.x != 0.0 || d.y != 0.0)
656 angle = atan2(d.y, d.x);
659 out->set_color(color_fill, get_outline_color());
660 out->text(center(), text, ntext, angle);
665 graphic_object::~graphic_object()
668 ad_delete(ntext) text;
671 class rectangle_object : public graphic_object {
676 rectangle_object(const position &);
677 double width() { return dim.x; }
678 double height() { return dim.y; }
679 position origin() { return cent; }
680 position center() { return cent; }
681 position north() { return position(cent.x, cent.y + dim.y/2.0); }
682 position south() { return position(cent.x, cent.y - dim.y/2.0); }
683 position east() { return position(cent.x + dim.x/2.0, cent.y); }
684 position west() { return position(cent.x - dim.x/2.0, cent.y); }
685 position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); }
686 position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); }
687 position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); }
688 position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); }
689 object_type type() = 0;
690 void update_bounding_box(bounding_box *);
691 void move_by(const position &);
694 rectangle_object::rectangle_object(const position &d)
699 void rectangle_object::update_bounding_box(bounding_box *p)
701 p->encompass(cent - dim/2.0);
702 p->encompass(cent + dim/2.0);
705 void rectangle_object::move_by(const position &a)
710 class closed_object : public rectangle_object {
712 closed_object(const position &);
713 object_type type() = 0;
714 void set_fill(double);
715 void set_xslanted(double);
716 void set_yslanted(double);
717 void set_fill_color(char *fill);
719 double fill; // < 0 if not filled
720 double xslanted; // !=0 if x is slanted
721 double yslanted; // !=0 if y is slanted
722 char *color_fill; // = 0 if not colored
725 closed_object::closed_object(const position &pos)
726 : rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
730 void closed_object::set_fill(double f)
736 /* accept positive and negative values */
737 void closed_object::set_xslanted(double s)
742 /* accept positive and negative values */
743 void closed_object::set_yslanted(double s)
749 void closed_object::set_fill_color(char *f)
751 color_fill = strsave(f);
754 class box_object : public closed_object {
758 box_object(const position &, double);
759 object_type type() { return BOX_OBJECT; }
761 position north_east();
762 position north_west();
763 position south_east();
764 position south_west();
767 box_object::box_object(const position &pos, double r)
768 : closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r)
772 const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
774 position box_object::north_east()
776 return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
777 cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
780 position box_object::north_west()
782 return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
783 cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
786 position box_object::south_east()
788 return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
789 cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
792 position box_object::south_west()
794 return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
795 cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
798 void box_object::print()
800 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
802 out->set_color(color_fill, graphic_object::get_outline_color());
804 distance dim2 = dim/2.0;
806 /* error("x slanted %1", xslanted); */
807 /* error("y slanted %1", yslanted); */
808 vec[0] = cent + position(dim2.x, -(dim2.y - yslanted)); /* lr */
809 vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted); /* ur */
810 vec[2] = cent + position(-(dim2.x - xslanted), dim2.y); /* ul */
811 vec[3] = cent + position(-(dim2.x), -dim2.y); /* ll */
812 out->polygon(vec, 4, lt, fill);
815 distance abs_dim(fabs(dim.x), fabs(dim.y));
816 out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill);
821 graphic_object *object_spec::make_box(position *curpos, direction *dirp)
823 static double last_box_height;
824 static double last_box_width;
825 static double last_box_radius;
826 static int have_last_box = 0;
827 if (!(flags & HAS_HEIGHT)) {
828 if ((flags & IS_SAME) && have_last_box)
829 height = last_box_height;
831 lookup_variable("boxht", &height);
833 if (!(flags & HAS_WIDTH)) {
834 if ((flags & IS_SAME) && have_last_box)
835 width = last_box_width;
837 lookup_variable("boxwid", &width);
839 if (!(flags & HAS_RADIUS)) {
840 if ((flags & IS_SAME) && have_last_box)
841 radius = last_box_radius;
843 lookup_variable("boxrad", &radius);
845 last_box_width = width;
846 last_box_height = height;
847 last_box_radius = radius;
849 radius = fabs(radius);
850 if (radius*2.0 > fabs(width))
851 radius = fabs(width/2.0);
852 if (radius*2.0 > fabs(height))
853 radius = fabs(height/2.0);
854 box_object *p = new box_object(position(width, height), radius);
855 if (!position_rectangle(p, curpos, dirp)) {
862 // return non-zero for success
864 int object_spec::position_rectangle(rectangle_object *p,
865 position *curpos, direction *dirp)
868 dir = *dirp; // ignore any direction in attribute list
872 motion.y = p->height()/2.0;
875 motion.y = -p->height()/2.0;
878 motion.x = -p->width()/2.0;
880 case RIGHT_DIRECTION:
881 motion.x = p->width()/2.0;
886 if (flags & HAS_AT) {
888 if (flags & HAS_WITH) {
892 if (!with->follow(here, &offset))
907 class block_object : public rectangle_object {
911 block_object(const position &, const object_list &ol, PTABLE(place) *t);
913 place *find_label(const char *);
915 void move_by(const position &);
919 block_object::block_object(const position &d, const object_list &ol,
921 : rectangle_object(d), oblist(ol), tbl(t)
925 block_object::~block_object()
928 object *p = oblist.head;
936 void block_object::print()
938 out->begin_block(south_west(), north_east());
939 print_object_list(oblist.head);
943 static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
945 // Adjust all the labels that aren't attached to objects.
946 PTABLE_ITERATOR(place) iter(tbl);
949 while (iter.next(&key, &pl))
950 if (key && csupper(key[0]) && pl->obj == 0) {
956 void block_object::move_by(const position &a)
959 for (object *p = oblist.head; p; p = p->next)
961 adjust_objectless_places(tbl, a);
965 place *block_object::find_label(const char *name)
967 return tbl->lookup(name);
970 object_type block_object::type()
975 graphic_object *object_spec::make_block(position *curpos, direction *dirp)
978 for (object *p = oblist.head; p; p = p->next)
979 p->update_bounding_box(&bb);
982 position m = -(bb.ll + bb.ur)/2.0;
983 for (object *p = oblist.head; p; p = p->next)
985 adjust_objectless_places(tbl, m);
988 if (flags & HAS_WIDTH)
990 if (flags & HAS_HEIGHT)
992 block_object *block = new block_object(dim, oblist, tbl);
993 if (!position_rectangle(block, curpos, dirp)) {
998 oblist.head = oblist.tail = 0;
1002 class text_object : public rectangle_object {
1004 text_object(const position &);
1005 object_type type() { return TEXT_OBJECT; }
1008 text_object::text_object(const position &d)
1009 : rectangle_object(d)
1013 graphic_object *object_spec::make_text(position *curpos, direction *dirp)
1015 if (!(flags & HAS_HEIGHT)) {
1016 lookup_variable("textht", &height);
1018 for (text_item *t = text; t; t = t->next)
1022 if (!(flags & HAS_WIDTH))
1023 lookup_variable("textwid", &width);
1024 text_object *p = new text_object(position(width, height));
1025 if (!position_rectangle(p, curpos, dirp)) {
1033 class ellipse_object : public closed_object {
1035 ellipse_object(const position &);
1036 position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1037 cent.y + dim.y/(M_SQRT2*2.0)); }
1038 position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1039 cent.y + dim.y/(M_SQRT2*2.0)); }
1040 position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1041 cent.y - dim.y/(M_SQRT2*2.0)); }
1042 position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1043 cent.y - dim.y/(M_SQRT2*2.0)); }
1044 double radius() { return dim.x/2.0; }
1045 object_type type() { return ELLIPSE_OBJECT; }
1049 ellipse_object::ellipse_object(const position &d)
1054 void ellipse_object::print()
1056 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1058 out->set_color(color_fill, graphic_object::get_outline_color());
1059 out->ellipse(cent, dim, lt, fill);
1063 graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
1065 static double last_ellipse_height;
1066 static double last_ellipse_width;
1067 static int have_last_ellipse = 0;
1068 if (!(flags & HAS_HEIGHT)) {
1069 if ((flags & IS_SAME) && have_last_ellipse)
1070 height = last_ellipse_height;
1072 lookup_variable("ellipseht", &height);
1074 if (!(flags & HAS_WIDTH)) {
1075 if ((flags & IS_SAME) && have_last_ellipse)
1076 width = last_ellipse_width;
1078 lookup_variable("ellipsewid", &width);
1080 last_ellipse_width = width;
1081 last_ellipse_height = height;
1082 have_last_ellipse = 1;
1083 ellipse_object *p = new ellipse_object(position(width, height));
1084 if (!position_rectangle(p, curpos, dirp)) {
1091 class circle_object : public ellipse_object {
1093 circle_object(double);
1094 object_type type() { return CIRCLE_OBJECT; }
1098 circle_object::circle_object(double diam)
1099 : ellipse_object(position(diam, diam))
1103 void circle_object::print()
1105 if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1107 out->set_color(color_fill, graphic_object::get_outline_color());
1108 out->circle(cent, dim.x/2.0, lt, fill);
1112 graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
1114 static double last_circle_radius;
1115 static int have_last_circle = 0;
1116 if (!(flags & HAS_RADIUS)) {
1117 if ((flags & IS_SAME) && have_last_circle)
1118 radius = last_circle_radius;
1120 lookup_variable("circlerad", &radius);
1122 last_circle_radius = radius;
1123 have_last_circle = 1;
1124 circle_object *p = new circle_object(radius*2.0);
1125 if (!position_rectangle(p, curpos, dirp)) {
1132 class move_object : public graphic_object {
1136 move_object(const position &s, const position &e);
1137 position origin() { return en; }
1138 object_type type() { return MOVE_OBJECT; }
1139 void update_bounding_box(bounding_box *);
1140 void move_by(const position &);
1143 move_object::move_object(const position &s, const position &e)
1148 void move_object::update_bounding_box(bounding_box *p)
1154 void move_object::move_by(const position &a)
1160 graphic_object *object_spec::make_move(position *curpos, direction *dirp)
1162 static position last_move;
1163 static int have_last_move = 0;
1165 // No need to look at at since 'at' attribute sets 'from' attribute.
1166 position startpos = (flags & HAS_FROM) ? from : *curpos;
1167 if (!(flags & HAS_SEGMENT)) {
1168 if ((flags & IS_SAME) && have_last_move)
1169 segment_pos = last_move;
1173 segment_pos.y = segment_height;
1175 case DOWN_DIRECTION:
1176 segment_pos.y = -segment_height;
1178 case LEFT_DIRECTION:
1179 segment_pos.x = -segment_width;
1181 case RIGHT_DIRECTION:
1182 segment_pos.x = segment_width;
1189 segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1190 // Reverse the segment_list so that it's in forward order.
1191 segment *old = segment_list;
1194 segment *tem = old->next;
1195 old->next = segment_list;
1199 // Compute the end position.
1200 position endpos = startpos;
1201 for (segment *s = segment_list; s; s = s->next)
1207 last_move = endpos - startpos;
1208 move_object *p = new move_object(startpos, endpos);
1213 class linear_object : public graphic_object {
1215 char arrow_at_start;
1217 arrow_head_type aht;
1221 linear_object(const position &s, const position &e);
1222 position start() { return strt; }
1223 position end() { return en; }
1224 void move_by(const position &);
1225 void update_bounding_box(bounding_box *) = 0;
1226 object_type type() = 0;
1227 void add_arrows(int at_start, int at_end, const arrow_head_type &);
1230 class line_object : public linear_object {
1235 line_object(const position &s, const position &e, position *, int);
1237 position origin() { return strt; }
1238 position center() { return (strt + en)/2.0; }
1239 position north() { return (en.y - strt.y) > 0 ? en : strt; }
1240 position south() { return (en.y - strt.y) < 0 ? en : strt; }
1241 position east() { return (en.x - strt.x) > 0 ? en : strt; }
1242 position west() { return (en.x - strt.x) < 0 ? en : strt; }
1243 object_type type() { return LINE_OBJECT; }
1244 void update_bounding_box(bounding_box *);
1246 void move_by(const position &);
1249 class arrow_object : public line_object {
1251 arrow_object(const position &, const position &, position *, int);
1252 object_type type() { return ARROW_OBJECT; }
1255 class spline_object : public line_object {
1257 spline_object(const position &, const position &, position *, int);
1258 object_type type() { return SPLINE_OBJECT; }
1260 void update_bounding_box(bounding_box *);
1263 linear_object::linear_object(const position &s, const position &e)
1264 : arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
1268 void linear_object::move_by(const position &a)
1274 void linear_object::add_arrows(int at_start, int at_end,
1275 const arrow_head_type &a)
1277 arrow_at_start = at_start;
1278 arrow_at_end = at_end;
1282 line_object::line_object(const position &s, const position &e,
1284 : linear_object(s, e), v(p), n(i)
1288 void line_object::print()
1290 if (lt.type == line_type::invisible)
1292 out->set_color(0, graphic_object::get_outline_color());
1293 // shorten line length to avoid arrow sticking.
1295 if (arrow_at_start) {
1296 position base = v[0] - strt;
1297 double hyp = hypot(base);
1299 error("cannot draw arrow on object with zero length");
1302 if (aht.solid && out->supports_filled_polygons()) {
1303 base *= aht.height / hyp;
1304 draw_arrow(strt, strt - v[0], aht, lt,
1305 graphic_object::get_outline_color());
1308 base *= fabs(lt.thickness) / hyp / 72 / 4;
1310 draw_arrow(sp, sp - v[0], aht, lt,
1311 graphic_object::get_outline_color());
1315 position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1316 double hyp = hypot(base);
1318 error("cannot draw arrow on object with zero length");
1321 if (aht.solid && out->supports_filled_polygons()) {
1322 base *= aht.height / hyp;
1323 draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1324 graphic_object::get_outline_color());
1327 base *= fabs(lt.thickness) / hyp / 72 / 4;
1329 draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1330 graphic_object::get_outline_color());
1333 out->line(sp, v, n, lt);
1337 void line_object::update_bounding_box(bounding_box *p)
1340 for (int i = 0; i < n; i++)
1344 void line_object::move_by(const position &pos)
1346 linear_object::move_by(pos);
1347 for (int i = 0; i < n; i++)
1351 void spline_object::update_bounding_box(bounding_box *p)
1363 [ the points for the Bezier cubic ]
1371 (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4
1372 [ the equation for the Bezier cubic ]
1374 = .125*q1 + .75*q2 + .125*q3
1377 for (int i = 1; i < n; i++)
1378 p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125);
1381 arrow_object::arrow_object(const position &s, const position &e,
1383 : line_object(s, e, p, i)
1387 spline_object::spline_object(const position &s, const position &e,
1389 : line_object(s, e, p, i)
1393 void spline_object::print()
1395 if (lt.type == line_type::invisible)
1397 out->set_color(0, graphic_object::get_outline_color());
1398 // shorten line length for spline to avoid arrow sticking
1400 if (arrow_at_start) {
1401 position base = v[0] - strt;
1402 double hyp = hypot(base);
1404 error("cannot draw arrow on object with zero length");
1407 if (aht.solid && out->supports_filled_polygons()) {
1408 base *= aht.height / hyp;
1409 draw_arrow(strt, strt - v[0], aht, lt,
1410 graphic_object::get_outline_color());
1411 sp = strt + base*0.1; // to reserve spline shape
1413 base *= fabs(lt.thickness) / hyp / 72 / 4;
1415 draw_arrow(sp, sp - v[0], aht, lt,
1416 graphic_object::get_outline_color());
1420 position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1421 double hyp = hypot(base);
1423 error("cannot draw arrow on object with zero length");
1426 if (aht.solid && out->supports_filled_polygons()) {
1427 base *= aht.height / hyp;
1428 draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1429 graphic_object::get_outline_color());
1430 v[n-1] = en - base*0.1; // to reserve spline shape
1432 base *= fabs(lt.thickness) / hyp / 72 / 4;
1434 draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1435 graphic_object::get_outline_color());
1438 out->spline(sp, v, n, lt);
1442 line_object::~line_object()
1447 linear_object *object_spec::make_line(position *curpos, direction *dirp)
1449 static position last_line;
1450 static int have_last_line = 0;
1452 // We handle 'at' only in conjunction with 'with', otherwise it is
1453 // the same as the 'from' attribute.
1455 if ((flags & HAS_AT) && (flags & HAS_WITH))
1456 // handled later -- we need the end position
1458 else if (flags & HAS_FROM)
1462 if (!(flags & HAS_SEGMENT)) {
1463 if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
1465 segment_pos = last_line;
1469 segment_pos.y = segment_height;
1471 case DOWN_DIRECTION:
1472 segment_pos.y = -segment_height;
1474 case LEFT_DIRECTION:
1475 segment_pos.x = -segment_width;
1477 case RIGHT_DIRECTION:
1478 segment_pos.x = segment_width;
1484 segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1485 // reverse the segment_list so that it's in forward order
1486 segment *old = segment_list;
1489 segment *tem = old->next;
1490 old->next = segment_list;
1494 // Absolutise all movements
1495 position endpos = startpos;
1498 for (s = segment_list; s; s = s->next, nsegments++)
1504 s->is_absolute = 1; // to avoid confusion
1506 if ((flags & HAS_AT) && (flags & HAS_WITH)) {
1507 // 'tmpobj' works for arrows and splines too -- we only need positions
1508 line_object tmpobj(startpos, endpos, 0, 0);
1513 if (!with->follow(here, &offset))
1516 for (s = segment_list; s; s = s->next)
1523 position *v = new position[nsegments];
1525 for (s = segment_list; s; s = s->next, i++)
1527 if (flags & IS_DEFAULT_CHOPPED) {
1528 lookup_variable("circlerad", &start_chop);
1529 end_chop = start_chop;
1530 flags |= IS_CHOPPED;
1532 if (flags & IS_CHOPPED) {
1533 position start_chop_vec, end_chop_vec;
1534 if (start_chop != 0.0) {
1535 start_chop_vec = v[0] - startpos;
1536 start_chop_vec *= start_chop / hypot(start_chop_vec);
1538 if (end_chop != 0.0) {
1539 end_chop_vec = (v[nsegments - 1]
1540 - (nsegments > 1 ? v[nsegments - 2] : startpos));
1541 end_chop_vec *= end_chop / hypot(end_chop_vec);
1543 startpos += start_chop_vec;
1544 v[nsegments - 1] -= end_chop_vec;
1545 endpos -= end_chop_vec;
1549 p = new spline_object(startpos, endpos, v, nsegments);
1552 p = new arrow_object(startpos, endpos, v, nsegments);
1555 p = new line_object(startpos, endpos, v, nsegments);
1561 last_line = endpos - startpos;
1566 class arc_object : public linear_object {
1571 arc_object(int, const position &, const position &, const position &);
1572 position origin() { return cent; }
1573 position center() { return cent; }
1574 double radius() { return rad; }
1579 position north_east();
1580 position north_west();
1581 position south_east();
1582 position south_west();
1583 void update_bounding_box(bounding_box *);
1584 object_type type() { return ARC_OBJECT; }
1586 void move_by(const position &pos);
1589 arc_object::arc_object(int cw, const position &s, const position &e,
1591 : linear_object(s, e), clockwise(cw), cent(c)
1596 void arc_object::move_by(const position &pos)
1598 linear_object::move_by(pos);
1602 // we get arc corners from the corresponding circle
1604 position arc_object::north()
1606 position result(cent);
1611 position arc_object::south()
1613 position result(cent);
1618 position arc_object::east()
1620 position result(cent);
1625 position arc_object::west()
1627 position result(cent);
1632 position arc_object::north_east()
1634 position result(cent);
1635 result.x += rad/M_SQRT2;
1636 result.y += rad/M_SQRT2;
1640 position arc_object::north_west()
1642 position result(cent);
1643 result.x -= rad/M_SQRT2;
1644 result.y += rad/M_SQRT2;
1648 position arc_object::south_east()
1650 position result(cent);
1651 result.x += rad/M_SQRT2;
1652 result.y -= rad/M_SQRT2;
1656 position arc_object::south_west()
1658 position result(cent);
1659 result.x -= rad/M_SQRT2;
1660 result.y -= rad/M_SQRT2;
1665 void arc_object::print()
1667 if (lt.type == line_type::invisible)
1669 out->set_color(0, graphic_object::get_outline_color());
1670 // handle arrow direction; make shorter line for arc
1679 if (arrow_at_start) {
1680 double theta = aht.height / rad;
1684 b = position(b.x*cos(theta) - b.y*sin(theta),
1685 b.x*sin(theta) + b.y*cos(theta)) + cent;
1690 if (aht.solid && out->supports_filled_polygons()) {
1691 draw_arrow(strt, strt - b, aht, lt,
1692 graphic_object::get_outline_color());
1695 theta = fabs(lt.thickness) / 72 / 4 / rad;
1699 b = position(b.x*cos(theta) - b.y*sin(theta),
1700 b.x*sin(theta) + b.y*cos(theta)) + cent;
1701 draw_arrow(b, b - v, aht, lt,
1702 graphic_object::get_outline_color());
1703 out->line(b, &v, 1, lt);
1707 double theta = aht.height / rad;
1711 b = position(b.x*cos(theta) - b.y*sin(theta),
1712 b.x*sin(theta) + b.y*cos(theta)) + cent;
1717 if (aht.solid && out->supports_filled_polygons()) {
1718 draw_arrow(en, en - b, aht, lt,
1719 graphic_object::get_outline_color());
1722 theta = fabs(lt.thickness) / 72 / 4 / rad;
1726 b = position(b.x*cos(theta) - b.y*sin(theta),
1727 b.x*sin(theta) + b.y*cos(theta)) + cent;
1728 draw_arrow(b, b - v, aht, lt,
1729 graphic_object::get_outline_color());
1730 out->line(b, &v, 1, lt);
1733 out->arc(sp, cent, ep, lt);
1737 inline double max(double a, double b)
1739 return a > b ? a : b;
1742 void arc_object::update_bounding_box(bounding_box *p)
1746 position start_offset = strt - cent;
1747 if (start_offset.x == 0.0 && start_offset.y == 0.0)
1749 position end_offset = en - cent;
1750 if (end_offset.x == 0.0 && end_offset.y == 0.0)
1752 double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0);
1753 double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0);
1755 double temp = start_quad;
1756 start_quad = end_quad;
1759 if (start_quad < 0.0)
1761 while (end_quad <= start_quad)
1763 double r = max(hypot(start_offset), hypot(end_offset));
1764 for (int q = int(start_quad) + 1; q < end_quad; q++) {
1780 p->encompass(cent + offset);
1784 // We ignore the with attribute. The at attribute always refers to the center.
1786 linear_object *object_spec::make_arc(position *curpos, direction *dirp)
1789 int cw = (flags & IS_CLOCKWISE) != 0;
1790 // compute the start
1792 if (flags & HAS_FROM)
1796 if (!(flags & HAS_RADIUS))
1797 lookup_variable("arcrad", &radius);
1803 position m(radius, radius);
1804 // Adjust the signs.
1806 if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1808 if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
1810 *dirp = direction((dir + 3) % 4);
1813 if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
1815 if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1817 *dirp = direction((dir + 1) % 4);
1819 endpos = startpos + m;
1821 // compute the center
1825 else if (startpos == endpos)
1826 centerpos = startpos;
1828 position h = (endpos - startpos)/2.0;
1829 double d = hypot(h);
1832 // make the radius big enough
1835 double alpha = acos(d/radius);
1836 double theta = atan2(h.y, h.x);
1841 centerpos = position(cos(theta), sin(theta))*radius + startpos;
1843 arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
1848 graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
1851 if (type == ARC_OBJECT)
1852 obj = make_arc(curpos, dirp);
1854 obj = make_line(curpos, dirp);
1855 if (type == ARROW_OBJECT
1856 && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0)
1857 flags |= HAS_RIGHT_ARROW_HEAD;
1858 if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) {
1860 int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0;
1861 int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0;
1862 if (flags & HAS_HEIGHT)
1865 lookup_variable("arrowht", &a.height);
1866 if (flags & HAS_WIDTH)
1869 lookup_variable("arrowwid", &a.width);
1871 lookup_variable("arrowhead", &solid);
1872 a.solid = solid != 0.0;
1873 obj->add_arrows(at_start, at_end, a);
1878 object *object_spec::make_object(position *curpos, direction *dirp)
1880 graphic_object *obj = 0;
1883 obj = make_block(curpos, dirp);
1886 obj = make_box(curpos, dirp);
1889 obj = make_text(curpos, dirp);
1891 case ELLIPSE_OBJECT:
1892 obj = make_ellipse(curpos, dirp);
1895 obj = make_circle(curpos, dirp);
1898 obj = make_move(curpos, dirp);
1904 obj = make_linear(curpos, dirp);
1913 if (flags & IS_INVISIBLE)
1914 obj->set_invisible();
1916 obj->add_text(text, (flags & IS_ALIGNED) != 0);
1917 if (flags & IS_DOTTED)
1918 obj->set_dotted(dash_width);
1919 else if (flags & IS_DASHED)
1920 obj->set_dashed(dash_width);
1922 if (flags & HAS_THICKNESS)
1925 lookup_variable("linethick", &th);
1926 obj->set_thickness(th);
1927 if (flags & IS_OUTLINED)
1928 obj->set_outline_color(outlined);
1929 if (flags & IS_XSLANTED)
1930 obj->set_xslanted(xslanted);
1931 if (flags & IS_YSLANTED)
1932 obj->set_yslanted(yslanted);
1933 if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) {
1934 if (flags & IS_SHADED)
1935 obj->set_fill_color(shaded);
1937 if (flags & IS_DEFAULT_FILLED)
1938 lookup_variable("fillval", &fill);
1940 error("bad fill value %1", fill);
1942 obj->set_fill(fill);
1949 struct string_list {
1952 string_list(char *);
1956 string_list::string_list(char *s)
1961 string_list::~string_list()
1966 /* A path is used to hold the argument to the 'with' attribute. For
1967 example, '.nw' or '.A.s' or '.A'. The major operation on a path is to
1968 take a place and follow the path through the place to place within the
1969 place. Note that '.A.B.C.sw' will work.
1971 For compatibility with DWB pic, 'with' accepts positions also (this
1972 is incorrectly documented in CSTR 116). */
1974 path::path(corner c)
1975 : crn(c), label_list(0), ypath(0), is_position(0)
1979 path::path(position p)
1980 : crn(0), label_list(0), ypath(0), is_position(1)
1986 path::path(char *l, corner c)
1987 : crn(c), ypath(0), is_position(0)
1989 label_list = new string_list(l);
1994 while (label_list) {
1995 string_list *tem = label_list;
1996 label_list = label_list->next;
2002 void path::append(corner c)
2008 void path::append(char *s)
2011 for (p = &label_list; *p; p = &(*p)->next)
2013 *p = new string_list(s);
2016 void path::set_ypath(path *p)
2021 // return non-zero for success
2023 int path::follow(const place &pl, place *result) const
2031 const place *p = &pl;
2032 for (string_list *lb = label_list; lb; lb = lb->next)
2033 if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) {
2034 lex_error("object does not contain a place '%1'", lb->str);
2037 if (crn == 0 || p->obj == 0)
2040 position ps = ((p->obj)->*(crn))();
2047 if (!ypath->follow(pl, &tem))
2050 if (result->obj != tem.obj)
2056 void print_object_list(object *p)
2058 for (; p; p = p->next) {
2064 void print_picture(object *obj)
2067 for (object *p = obj; p; p = p->next)
2068 p->update_bounding_box(&bb);
2070 lookup_variable("scale", &scale);
2071 out->start_picture(scale, bb.ll, bb.ur);
2072 print_object_list(obj);
2073 out->finish_picture();