Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / preproc / pic / object.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005
3      Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 #include "pic.h"
23 #include "ptable.h"
24 #include "object.h"
25
26 void print_object_list(object *);
27
28 line_type::line_type()
29 : type(solid), thickness(1.0)
30 {
31 }
32
33 output::output() : args(0), desired_height(0.0), desired_width(0.0)
34 {
35 }
36
37 output::~output()
38 {
39   a_delete args;
40 }
41
42 void output::set_desired_width_height(double wid, double ht)
43 {
44   desired_width = wid;
45   desired_height = ht;
46 }
47
48 void output::set_args(const char *s)
49 {
50   a_delete args;
51   if (s == 0 || *s == '\0')
52     args = 0;
53   else
54     args = strsave(s);
55 }
56
57 int output::supports_filled_polygons()
58 {
59   return 0;
60 }
61
62 void output::begin_block(const position &, const position &)
63 {
64 }
65
66 void output::end_block()
67 {
68 }
69
70 double output::compute_scale(double sc, const position &ll, const position &ur)
71 {
72   distance dim = ur - ll;
73   if (desired_width != 0.0 || desired_height != 0.0) {
74     sc = 0.0;
75     if (desired_width != 0.0) {
76       if (dim.x == 0.0)
77         error("width specified for picture with zero width");
78       else
79         sc = dim.x/desired_width;
80     }
81     if (desired_height != 0.0) {
82       if (dim.y == 0.0)
83         error("height specified for picture with zero height");
84       else {
85         double tem = dim.y/desired_height;
86         if (tem > sc)
87           sc = tem;
88       }
89     }
90     return sc == 0.0 ? 1.0 : sc;
91   }
92   else {
93     if (sc <= 0.0)
94       sc = 1.0;
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;
105     }
106     else
107       return sc;
108   }
109 }
110
111 position::position(const place &pl)
112 {
113   if (pl.obj != 0) {
114     // Use two statements to work around bug in SGI C++.
115     object *tem = pl.obj;
116     *this = tem->origin();
117   }
118   else {
119     x = pl.x;
120     y = pl.y;
121   }
122 }
123
124 position::position() : x(0.0), y(0.0)
125 {
126 }
127
128 position::position(double a, double b) : x(a), y(b)
129 {
130 }
131
132
133 int operator==(const position &a, const position &b)
134 {
135   return a.x == b.x && a.y == b.y;
136 }
137
138 int operator!=(const position &a, const position &b)
139 {
140   return a.x != b.x || a.y != b.y;
141 }
142
143 position &position::operator+=(const position &a)
144 {
145   x += a.x;
146   y += a.y;
147   return *this;
148 }
149
150 position &position::operator-=(const position &a)
151 {
152   x -= a.x;
153   y -= a.y;
154   return *this;
155 }
156
157 position &position::operator*=(double a)
158 {
159   x *= a;
160   y *= a;
161   return *this;
162 }
163
164 position &position::operator/=(double a)
165 {
166   x /= a;
167   y /= a;
168   return *this;
169 }
170
171 position operator-(const position &a)
172 {
173   return position(-a.x, -a.y);
174 }
175
176 position operator+(const position &a, const position &b)
177 {
178   return position(a.x + b.x, a.y + b.y);
179 }
180
181 position operator-(const position &a, const position &b)
182 {
183   return position(a.x - b.x, a.y - b.y);
184 }
185
186 position operator/(const position &a, double n)
187 {
188   return position(a.x/n, a.y/n);
189 }
190
191 position operator*(const position &a, double n)
192 {
193   return position(a.x*n, a.y*n);
194 }
195
196 // dot product
197
198 double operator*(const position &a, const position &b)
199 {
200   return a.x*b.x + a.y*b.y;
201 }
202
203 double hypot(const position &a)
204 {
205   return groff_hypot(a.x, a.y);
206 }
207
208 struct arrow_head_type {
209   double height;
210   double width;
211   int solid;
212 };
213
214 void draw_arrow(const position &pos, const distance &dir,
215                 const arrow_head_type &aht, const line_type &lt,
216                 char *outline_color_for_fill)
217 {
218   double hyp = hypot(dir);
219   if (hyp == 0.0) {
220     error("cannot draw arrow on object with zero length");
221     return;
222   }
223   position base = -dir;
224   base *= aht.height/hyp;
225   position n(dir.y, -dir.x);
226   n *= aht.width/(hyp*2.0);
227   line_type slt = lt;
228   slt.type = line_type::solid;
229   if (aht.solid && out->supports_filled_polygons()) {
230     position v[3];
231     v[0] = pos;
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
237     slt.thickness = 0.1;
238     out->polygon(v, 3, slt, 1);
239   }
240   else {
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);
244   }
245 }
246
247 object::object() : prev(0), next(0)
248 {
249 }
250
251 object::~object()
252 {
253 }
254
255 void object::move_by(const position &)
256 {
257 }
258
259 void object::print()
260 {
261 }
262
263 void object::print_text()
264 {
265 }
266
267 int object::blank()
268 {
269   return 0;
270 }
271
272 struct bounding_box {
273   int blank;
274   position ll;
275   position ur;
276
277   bounding_box();
278   void encompass(const position &);
279 };
280
281 bounding_box::bounding_box()
282 : blank(1)
283 {
284 }
285
286 void bounding_box::encompass(const position &pos)
287 {
288   if (blank) {
289     ll = pos;
290     ur = pos;
291     blank = 0;
292   }
293   else {
294     if (pos.x < ll.x)
295       ll.x = pos.x;
296     if (pos.y < ll.y)
297       ll.y = pos.y;
298     if (pos.x > ur.x)
299       ur.x = pos.x;
300     if (pos.y > ur.y)
301       ur.y = pos.y;
302   }
303 }
304
305 void object::update_bounding_box(bounding_box *)
306 {
307 }
308
309 position object::origin()
310 {
311   return position(0.0,0.0);
312 }
313
314 position object::north()
315 {
316   return origin();
317 }
318
319 position object::south()
320 {
321   return origin();
322 }
323
324 position object::east()
325 {
326   return origin();
327 }
328
329 position object::west()
330 {
331   return origin();
332 }
333
334 position object::north_east()
335 {
336   return origin();
337 }
338
339 position object::north_west()
340 {
341   return origin();
342 }
343
344 position object::south_east()
345 {
346   return origin();
347 }
348
349 position object::south_west()
350 {
351   return origin();
352 }
353
354 position object::start()
355 {
356   return origin();
357 }
358
359 position object::end()
360 {
361   return origin();
362 }
363
364 position object::center()
365 {
366   return origin();
367 }
368
369 double object::width()
370 {
371   return 0.0;
372 }
373
374 double object::radius()
375 {
376   return 0.0;
377 }
378
379 double object::height()
380 {
381   return 0.0;
382 }
383
384 place *object::find_label(const char *)
385 {
386   return 0;
387 }
388
389 segment::segment(const position &a, int n, segment *p)
390 : is_absolute(n), pos(a), next(p)
391 {
392 }
393
394 text_item::text_item(char *t, const char *fn, int ln)
395 : next(0), text(t), filename(fn), lineno(ln) 
396 {
397   adj.h = CENTER_ADJUST;
398   adj.v = NONE_ADJUST;
399 }
400
401 text_item::~text_item()
402 {
403   a_delete text;
404 }
405
406 object_spec::object_spec(object_type t) : type(t)
407 {
408   flags = 0;
409   tbl = 0;
410   segment_list = 0;
411   segment_width = segment_height = 0.0;
412   segment_is_absolute = 0;
413   text = 0;
414   shaded = 0;
415   outlined = 0;
416   with = 0;
417   dir = RIGHT_DIRECTION;
418 }
419
420 object_spec::~object_spec()
421 {
422   delete tbl;
423   while (segment_list != 0) {
424     segment *tem = segment_list;
425     segment_list = segment_list->next;
426     delete tem;
427   }
428   object *p = oblist.head;
429   while (p != 0) {
430     object *tem = p;
431     p = p->next;
432     delete tem;
433   }
434   while (text != 0) {
435     text_item *tem = text;
436     text = text->next;
437     delete tem;
438   }
439   delete with;
440   a_delete shaded;
441   a_delete outlined;
442 }
443
444 class command_object : public object {
445   char *s;
446   const char *filename;
447   int lineno;
448 public:
449   command_object(char *, const char *, int);
450   ~command_object();
451   object_type type() { return OTHER_OBJECT; }
452   void print();
453 };
454
455 command_object::command_object(char *p, const char *fn, int ln)
456 : s(p), filename(fn), lineno(ln)
457 {
458 }
459
460 command_object::~command_object()
461 {
462   a_delete s;
463 }
464
465 void command_object::print()
466 {
467   out->command(s, filename, lineno);
468 }
469
470 object *make_command_object(char *s, const char *fn, int ln)
471 {
472   return new command_object(s, fn, ln);
473 }
474
475 class mark_object : public object {
476 public:
477   mark_object();
478   object_type type();
479 };
480
481 object *make_mark_object()
482 {
483   return new mark_object();
484 }
485
486 mark_object::mark_object()
487 {
488 }
489
490 object_type mark_object::type()
491 {
492   return MARK_OBJECT;
493 }
494
495 object_list::object_list() : head(0), tail(0)
496 {
497 }
498
499 void object_list::append(object *obj)
500 {
501   if (tail == 0) {
502     obj->next = obj->prev = 0;
503     head = tail = obj;
504   }
505   else {
506     obj->prev = tail;
507     obj->next = 0;
508     tail->next = obj;
509     tail = obj;
510   }
511 }
512
513 void object_list::wrap_up_block(object_list *ol)
514 {
515   object *p;
516   for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev)
517     ;
518   assert(p != 0);
519   ol->head = p->next;
520   if (ol->head) {
521     ol->tail = tail;
522     ol->head->prev = 0;
523   }
524   else
525     ol->tail = 0;
526   tail = p->prev;
527   if (tail)
528     tail->next = 0;
529   else
530     head = 0;
531   delete p;
532 }
533
534 text_piece::text_piece()
535 : text(0), filename(0), lineno(-1)
536 {
537   adj.h = CENTER_ADJUST;
538   adj.v = NONE_ADJUST;
539 }
540
541 text_piece::~text_piece()
542 {
543   a_delete text;
544 }
545
546 class graphic_object : public object {
547   int ntext;
548   text_piece *text;
549   int aligned;
550 protected:
551   line_type lt;
552   char *outline_color;
553   char *color_fill;
554 public:
555   graphic_object();
556   ~graphic_object();
557   object_type type() = 0;
558   void print_text();
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_fill_color(char *);
568 };
569
570 graphic_object::graphic_object()
571 : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
572 {
573 }
574
575 void graphic_object::set_dotted(double wid)
576 {
577   lt.type = line_type::dotted;
578   lt.dash_width = wid;
579 }
580
581 void graphic_object::set_dashed(double wid)
582 {
583   lt.type = line_type::dashed;
584   lt.dash_width = wid;
585 }
586
587 void graphic_object::set_thickness(double th)
588 {
589   lt.thickness = th;
590 }
591
592 void graphic_object::set_fill(double)
593 {
594 }
595
596 void graphic_object::set_fill_color(char *c)
597 {
598   color_fill = strsave(c);
599 }
600
601 void graphic_object::set_outline_color(char *c)
602 {
603   outline_color = strsave(c);
604 }
605
606 char *graphic_object::get_outline_color()
607 {
608   return outline_color;
609 }
610
611 void graphic_object::set_invisible()
612 {
613   lt.type = line_type::invisible;
614 }
615
616 void graphic_object::add_text(text_item *t, int a)
617 {
618   aligned = a;
619   int len = 0;
620   text_item *p;
621   for (p = t; p; p = p->next)
622     len++;
623   if (len == 0)
624     text = 0;
625   else {
626     text = new text_piece[len];
627     for (p = t, len = 0; p; p = p->next, len++) {
628       text[len].text = p->text;
629       p->text = 0;
630       text[len].adj = p->adj;
631       text[len].filename = p->filename;
632       text[len].lineno = p->lineno;
633     }
634   }
635   ntext = len;
636 }
637
638 void graphic_object::print_text()
639 {
640   double angle = 0.0;
641   if (aligned) {
642     position d(end() - start());
643     if (d.x != 0.0 || d.y != 0.0)
644       angle = atan2(d.y, d.x);
645   }
646   if (text != 0) {
647     out->set_color(color_fill, get_outline_color());
648     out->text(center(), text, ntext, angle);
649     out->reset_color();
650   }
651 }
652
653 graphic_object::~graphic_object()
654 {
655   if (text)
656     ad_delete(ntext) text;
657 }
658
659 class rectangle_object : public graphic_object {
660 protected:
661   position cent;
662   position dim;
663 public:
664   rectangle_object(const position &);
665   double width() { return dim.x; }
666   double height() { return dim.y; }
667   position origin() { return cent; }
668   position center() { return cent; }
669   position north() { return position(cent.x, cent.y + dim.y/2.0); }
670   position south() { return position(cent.x, cent.y - dim.y/2.0); }
671   position east() { return position(cent.x + dim.x/2.0, cent.y); }
672   position west() { return position(cent.x - dim.x/2.0, cent.y); }
673   position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); }
674   position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); }
675   position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); }
676   position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); }
677   object_type type() = 0;
678   void update_bounding_box(bounding_box *);
679   void move_by(const position &);
680 };
681
682 rectangle_object::rectangle_object(const position &d)
683 : dim(d)
684 {
685 }
686
687 void rectangle_object::update_bounding_box(bounding_box *p)
688 {
689   p->encompass(cent - dim/2.0);
690   p->encompass(cent + dim/2.0);
691 }
692
693 void rectangle_object::move_by(const position &a)
694 {
695   cent += a;
696 }
697
698 class closed_object : public rectangle_object {
699 public:
700   closed_object(const position &);
701   object_type type() = 0;
702   void set_fill(double);
703   void set_fill_color(char *fill);
704 protected:
705   double fill;                  // < 0 if not filled
706   char *color_fill;             // = 0 if not colored
707 };
708
709 closed_object::closed_object(const position &pos)
710 : rectangle_object(pos), fill(-1.0), color_fill(0)
711 {
712 }
713
714 void closed_object::set_fill(double f)
715 {
716   assert(f >= 0.0);
717   fill = f;
718 }
719
720 void closed_object::set_fill_color(char *f)
721 {
722   color_fill = strsave(f);
723 }
724
725 class box_object : public closed_object {
726   double xrad;
727   double yrad;
728 public:
729   box_object(const position &, double);
730   object_type type() { return BOX_OBJECT; }
731   void print();
732   position north_east();
733   position north_west();
734   position south_east();
735   position south_west();
736 };
737
738 box_object::box_object(const position &pos, double r)
739 : closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r)
740 {
741 }
742
743 const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
744
745 position box_object::north_east()
746 {
747   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
748                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
749 }
750
751 position box_object::north_west()
752 {
753   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
754                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
755 }
756
757 position box_object::south_east()
758 {
759   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
760                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
761 }
762
763 position box_object::south_west()
764 {
765   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
766                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
767 }
768
769 void box_object::print()
770 {
771   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
772     return;
773   out->set_color(color_fill, graphic_object::get_outline_color());
774   if (xrad == 0.0) {
775     distance dim2 = dim/2.0;
776     position vec[4];
777     vec[0] = cent + position(dim2.x, -dim2.y);
778     vec[1] = cent + position(dim2.x, dim2.y);
779     vec[2] = cent + position(-dim2.x, dim2.y);
780     vec[3] = cent + position(-dim2.x, -dim2.y);
781     out->polygon(vec, 4, lt, fill);
782   }
783   else {
784     distance abs_dim(fabs(dim.x), fabs(dim.y));
785     out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill);
786   }
787   out->reset_color();
788 }
789
790 graphic_object *object_spec::make_box(position *curpos, direction *dirp)
791 {
792   static double last_box_height;
793   static double last_box_width;
794   static double last_box_radius;
795   static int have_last_box = 0;
796   if (!(flags & HAS_HEIGHT)) {
797     if ((flags & IS_SAME) && have_last_box)
798       height = last_box_height;
799     else
800       lookup_variable("boxht", &height);
801   }
802   if (!(flags & HAS_WIDTH)) {
803     if ((flags & IS_SAME) && have_last_box)
804       width = last_box_width;
805     else
806       lookup_variable("boxwid", &width);
807   }
808   if (!(flags & HAS_RADIUS)) {
809     if ((flags & IS_SAME) && have_last_box)
810       radius = last_box_radius;
811     else
812       lookup_variable("boxrad", &radius);
813   }
814   last_box_width = width;
815   last_box_height = height;
816   last_box_radius = radius;
817   have_last_box = 1;
818   radius = fabs(radius);
819   if (radius*2.0 > fabs(width))
820     radius = fabs(width/2.0);
821   if (radius*2.0 > fabs(height))
822     radius = fabs(height/2.0);
823   box_object *p = new box_object(position(width, height), radius);
824   if (!position_rectangle(p, curpos, dirp)) {
825     delete p;
826     p = 0;
827   }
828   return p;
829 }
830
831 // return non-zero for success
832
833 int object_spec::position_rectangle(rectangle_object *p,
834                                     position *curpos, direction *dirp)
835 {
836   position pos;
837   dir = *dirp;                  // ignore any direction in attribute list
838   position motion;
839   switch (dir) {
840   case UP_DIRECTION:
841     motion.y = p->height()/2.0;
842     break;
843   case DOWN_DIRECTION:
844     motion.y = -p->height()/2.0;
845     break;
846   case LEFT_DIRECTION:
847     motion.x = -p->width()/2.0;
848     break;
849   case RIGHT_DIRECTION:
850     motion.x = p->width()/2.0;
851     break;
852   default:
853     assert(0);
854   }
855   if (flags & HAS_AT) {
856     pos = at;
857     if (flags & HAS_WITH) {
858       place offset;
859       place here;
860       here.obj = p;
861       if (!with->follow(here, &offset))
862         return 0;
863       pos -= offset;
864     }
865   }
866   else {
867     pos = *curpos;
868     pos += motion;
869   }
870   p->move_by(pos);
871   pos += motion;
872   *curpos = pos;
873   return 1;
874 }
875
876 class block_object : public rectangle_object {
877   object_list oblist;
878   PTABLE(place) *tbl;
879 public:
880   block_object(const position &, const object_list &ol, PTABLE(place) *t);
881   ~block_object();
882   place *find_label(const char *);
883   object_type type();
884   void move_by(const position &);
885   void print();
886 };
887
888 block_object::block_object(const position &d, const object_list &ol,
889                            PTABLE(place) *t)
890 : rectangle_object(d), oblist(ol), tbl(t)
891 {
892 }
893
894 block_object::~block_object()
895 {
896   delete tbl;
897   object *p = oblist.head;
898   while (p != 0) {
899     object *tem = p;
900     p = p->next;
901     delete tem;
902   }
903 }
904
905 void block_object::print()
906 {
907   out->begin_block(south_west(), north_east());
908   print_object_list(oblist.head);
909   out->end_block();
910 }
911
912 static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
913 {
914   // Adjust all the labels that aren't attached to objects.
915   PTABLE_ITERATOR(place) iter(tbl);
916   const char *key;
917   place *pl;
918   while (iter.next(&key, &pl))
919     if (key && csupper(key[0]) && pl->obj == 0) {
920       pl->x += a.x;
921       pl->y += a.y;
922     }
923 }
924
925 void block_object::move_by(const position &a)
926 {
927   cent += a;
928   for (object *p = oblist.head; p; p = p->next)
929     p->move_by(a);
930   adjust_objectless_places(tbl, a);
931 }
932
933
934 place *block_object::find_label(const char *name)
935 {
936   return tbl->lookup(name);
937 }
938
939 object_type block_object::type()
940 {
941   return BLOCK_OBJECT;
942 }
943
944 graphic_object *object_spec::make_block(position *curpos, direction *dirp)
945 {
946   bounding_box bb;
947   for (object *p = oblist.head; p; p = p->next)
948     p->update_bounding_box(&bb);
949   position dim;
950   if (!bb.blank) {
951     position m = -(bb.ll + bb.ur)/2.0;
952     for (object *p = oblist.head; p; p = p->next)
953       p->move_by(m);
954     adjust_objectless_places(tbl, m);
955     dim = bb.ur - bb.ll;
956   }
957   if (flags & HAS_WIDTH)
958     dim.x = width;
959   if (flags & HAS_HEIGHT)
960     dim.y = height;
961   block_object *block = new block_object(dim, oblist, tbl);
962   if (!position_rectangle(block, curpos, dirp)) {
963     delete block;
964     block = 0;
965   }
966   tbl = 0;
967   oblist.head = oblist.tail = 0;
968   return block;
969 }
970
971 class text_object : public rectangle_object {
972 public:
973   text_object(const position &);
974   object_type type() { return TEXT_OBJECT; }
975 };
976
977 text_object::text_object(const position &d)
978 : rectangle_object(d)
979 {
980 }
981
982 graphic_object *object_spec::make_text(position *curpos, direction *dirp)
983 {
984   if (!(flags & HAS_HEIGHT)) {
985     lookup_variable("textht", &height);
986     int nitems = 0;
987     for (text_item *t = text; t; t = t->next)
988       nitems++;
989     height *= nitems;
990   }
991   if (!(flags & HAS_WIDTH))
992     lookup_variable("textwid", &width);
993   text_object *p = new text_object(position(width, height));
994   if (!position_rectangle(p, curpos, dirp)) {
995     delete p;
996     p = 0;
997   }
998   return p;
999 }
1000
1001
1002 class ellipse_object : public closed_object {
1003 public:
1004   ellipse_object(const position &);
1005   position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1006                                           cent.y + dim.y/(M_SQRT2*2.0)); }
1007   position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1008                                           cent.y + dim.y/(M_SQRT2*2.0)); }
1009   position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1010                                           cent.y - dim.y/(M_SQRT2*2.0)); }
1011   position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1012                                           cent.y - dim.y/(M_SQRT2*2.0)); }
1013   double radius() { return dim.x/2.0; }
1014   object_type type() { return ELLIPSE_OBJECT; }
1015   void print();
1016 };
1017
1018 ellipse_object::ellipse_object(const position &d)
1019 : closed_object(d)
1020 {
1021 }
1022
1023 void ellipse_object::print()
1024 {
1025   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1026     return;
1027   out->set_color(color_fill, graphic_object::get_outline_color());
1028   out->ellipse(cent, dim, lt, fill);
1029   out->reset_color();
1030 }
1031
1032 graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
1033 {
1034   static double last_ellipse_height;
1035   static double last_ellipse_width;
1036   static int have_last_ellipse = 0;
1037   if (!(flags & HAS_HEIGHT)) {
1038     if ((flags & IS_SAME) && have_last_ellipse)
1039       height = last_ellipse_height;
1040     else
1041       lookup_variable("ellipseht", &height);
1042   }
1043   if (!(flags & HAS_WIDTH)) {
1044     if ((flags & IS_SAME) && have_last_ellipse)
1045       width = last_ellipse_width;
1046     else
1047       lookup_variable("ellipsewid", &width);
1048   }
1049   last_ellipse_width = width;
1050   last_ellipse_height = height;
1051   have_last_ellipse = 1;
1052   ellipse_object *p = new ellipse_object(position(width, height));
1053   if (!position_rectangle(p, curpos, dirp)) {
1054     delete p;
1055     return 0;
1056   }
1057   return p;
1058 }
1059
1060 class circle_object : public ellipse_object {
1061 public:
1062   circle_object(double);
1063   object_type type() { return CIRCLE_OBJECT; }
1064   void print();
1065 };
1066
1067 circle_object::circle_object(double diam)
1068 : ellipse_object(position(diam, diam))
1069 {
1070 }
1071
1072 void circle_object::print()
1073 {
1074   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1075     return;
1076   out->set_color(color_fill, graphic_object::get_outline_color());
1077   out->circle(cent, dim.x/2.0, lt, fill);
1078   out->reset_color();
1079 }
1080
1081 graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
1082 {
1083   static double last_circle_radius;
1084   static int have_last_circle = 0;
1085   if (!(flags & HAS_RADIUS)) {
1086     if ((flags & IS_SAME) && have_last_circle)
1087       radius = last_circle_radius;
1088     else
1089       lookup_variable("circlerad", &radius);
1090   }
1091   last_circle_radius = radius;
1092   have_last_circle = 1;
1093   circle_object *p = new circle_object(radius*2.0);
1094   if (!position_rectangle(p, curpos, dirp)) {
1095     delete p;
1096     return 0;
1097   }
1098   return p;
1099 }
1100
1101 class move_object : public graphic_object {
1102   position strt;
1103   position en;
1104 public:
1105   move_object(const position &s, const position &e);
1106   position origin() { return en; }
1107   object_type type() { return MOVE_OBJECT; }
1108   void update_bounding_box(bounding_box *);
1109   void move_by(const position &);
1110 };
1111
1112 move_object::move_object(const position &s, const position &e)
1113 : strt(s), en(e)
1114 {
1115 }
1116
1117 void move_object::update_bounding_box(bounding_box *p)
1118 {
1119   p->encompass(strt);
1120   p->encompass(en);
1121 }
1122
1123 void move_object::move_by(const position &a)
1124 {
1125   strt += a;
1126   en += a;
1127 }
1128
1129 graphic_object *object_spec::make_move(position *curpos, direction *dirp)
1130 {
1131   static position last_move;
1132   static int have_last_move = 0;
1133   *dirp = dir;
1134   // No need to look at at since `at' attribute sets `from' attribute.
1135   position startpos = (flags & HAS_FROM) ? from : *curpos;
1136   if (!(flags & HAS_SEGMENT)) {
1137     if ((flags & IS_SAME) && have_last_move)
1138       segment_pos = last_move;
1139     else {
1140       switch (dir) {
1141       case UP_DIRECTION:
1142         segment_pos.y = segment_height;
1143         break;
1144       case DOWN_DIRECTION:
1145         segment_pos.y = -segment_height;
1146         break;
1147       case LEFT_DIRECTION:
1148         segment_pos.x = -segment_width;
1149         break;
1150       case RIGHT_DIRECTION:
1151         segment_pos.x = segment_width;
1152         break;
1153       default:
1154         assert(0);
1155       }
1156     }
1157   }
1158   segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1159   // Reverse the segment_list so that it's in forward order.
1160   segment *old = segment_list;
1161   segment_list = 0;
1162   while (old != 0) {
1163     segment *tem = old->next;
1164     old->next = segment_list;
1165     segment_list = old;
1166     old = tem;
1167   }
1168   // Compute the end position.
1169   position endpos = startpos;
1170   for (segment *s = segment_list; s; s = s->next)
1171     if (s->is_absolute)
1172       endpos = s->pos;
1173     else 
1174       endpos += s->pos;
1175   have_last_move = 1;
1176   last_move = endpos - startpos;
1177   move_object *p = new move_object(startpos, endpos);
1178   *curpos = endpos;
1179   return p;
1180 }
1181
1182 class linear_object : public graphic_object {
1183 protected:
1184   char arrow_at_start;
1185   char arrow_at_end;
1186   arrow_head_type aht;
1187   position strt;
1188   position en;
1189 public:
1190   linear_object(const position &s, const position &e);
1191   position start() { return strt; }
1192   position end() { return en; }
1193   void move_by(const position &);
1194   void update_bounding_box(bounding_box *) = 0;
1195   object_type type() = 0;
1196   void add_arrows(int at_start, int at_end, const arrow_head_type &);
1197 };
1198
1199 class line_object : public linear_object {
1200 protected:
1201   position *v;
1202   int n;
1203 public:
1204   line_object(const position &s, const position &e, position *, int);
1205   ~line_object();
1206   position origin() { return strt; }
1207   position center() { return (strt + en)/2.0; }
1208   position north() { return (en.y - strt.y) > 0 ? en : strt; }
1209   position south() { return (en.y - strt.y) < 0 ? en : strt; }
1210   position east() { return (en.x - strt.x) > 0 ? en : strt; }
1211   position west() { return (en.x - strt.x) < 0 ? en : strt; }
1212   object_type type() { return LINE_OBJECT; }
1213   void update_bounding_box(bounding_box *);
1214   void print();
1215   void move_by(const position &);
1216 };
1217
1218 class arrow_object : public line_object {
1219 public:
1220   arrow_object(const position &, const position &, position *, int);
1221   object_type type() { return ARROW_OBJECT; }
1222 };
1223
1224 class spline_object : public line_object {
1225 public:
1226   spline_object(const position &, const position &, position *, int);
1227   object_type type() { return SPLINE_OBJECT; }
1228   void print();
1229   void update_bounding_box(bounding_box *);
1230 };
1231
1232 linear_object::linear_object(const position &s, const position &e)
1233 : arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
1234 {
1235 }
1236
1237 void linear_object::move_by(const position &a)
1238 {
1239   strt += a;
1240   en += a;
1241 }
1242
1243 void linear_object::add_arrows(int at_start, int at_end,
1244                                const arrow_head_type &a)
1245 {
1246   arrow_at_start = at_start;
1247   arrow_at_end = at_end;
1248   aht = a;
1249 }
1250
1251 line_object::line_object(const position &s, const position &e,
1252                          position *p, int i)
1253 : linear_object(s, e), v(p), n(i)
1254 {
1255 }
1256
1257 void line_object::print()
1258 {
1259   if (lt.type == line_type::invisible)
1260     return;
1261   out->set_color(0, graphic_object::get_outline_color());
1262   // shorten line length to avoid arrow sticking.
1263   position sp = strt;
1264   if (arrow_at_start) {
1265     position base = v[0] - strt;
1266     double hyp = hypot(base);
1267     if (hyp == 0.0) {
1268       error("cannot draw arrow on object with zero length");
1269       return;
1270     }
1271     if (aht.solid && out->supports_filled_polygons()) {
1272       base *= aht.height / hyp;
1273       draw_arrow(strt, strt - v[0], aht, lt,
1274                  graphic_object::get_outline_color());
1275       sp = strt + base;
1276     } else {
1277       base *= fabs(lt.thickness) / hyp / 72 / 4;
1278       sp = strt + base;
1279       draw_arrow(sp, sp - v[0], aht, lt,
1280                  graphic_object::get_outline_color());
1281     }
1282   }
1283   if (arrow_at_end) {
1284     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1285     double hyp = hypot(base);
1286     if (hyp == 0.0) {
1287       error("cannot draw arrow on object with zero length");
1288       return;
1289     }
1290     if (aht.solid && out->supports_filled_polygons()) {
1291       base *= aht.height / hyp;
1292       draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1293                  graphic_object::get_outline_color());
1294       v[n-1] = en - base;
1295     } else {
1296       base *= fabs(lt.thickness) / hyp / 72 / 4;
1297       v[n-1] = en - base;
1298       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1299                  graphic_object::get_outline_color());
1300     }
1301   }
1302   out->line(sp, v, n, lt);
1303   out->reset_color();
1304 }
1305
1306 void line_object::update_bounding_box(bounding_box *p)
1307 {
1308   p->encompass(strt);
1309   for (int i = 0; i < n; i++)
1310     p->encompass(v[i]);
1311 }
1312
1313 void line_object::move_by(const position &pos)
1314 {
1315   linear_object::move_by(pos);
1316   for (int i = 0; i < n; i++)
1317     v[i] += pos;
1318 }
1319   
1320 void spline_object::update_bounding_box(bounding_box *p)
1321 {
1322   p->encompass(strt);
1323   p->encompass(en);
1324   /*
1325
1326   If
1327
1328   p1 = q1/2 + q2/2
1329   p2 = q1/6 + q2*5/6
1330   p3 = q2*5/6 + q3/6
1331   p4 = q2/2 + q3/2
1332   [ the points for the Bezier cubic ]
1333
1334   and
1335
1336   t = .5
1337
1338   then
1339
1340   (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4
1341   [ the equation for the Bezier cubic ]
1342
1343   = .125*q1 + .75*q2 + .125*q3
1344
1345   */
1346   for (int i = 1; i < n; i++)
1347     p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125);
1348 }
1349
1350 arrow_object::arrow_object(const position &s, const position &e,
1351                            position *p, int i)
1352 : line_object(s, e, p, i)
1353 {
1354 }
1355
1356 spline_object::spline_object(const position &s, const position &e,
1357                              position *p, int i)
1358 : line_object(s, e, p, i)
1359 {
1360 }
1361
1362 void spline_object::print()
1363 {
1364   if (lt.type == line_type::invisible)
1365     return;
1366   out->set_color(0, graphic_object::get_outline_color());
1367   // shorten line length for spline to avoid arrow sticking
1368   position sp = strt;
1369   if (arrow_at_start) {
1370     position base = v[0] - strt;
1371     double hyp = hypot(base);
1372     if (hyp == 0.0) {
1373       error("cannot draw arrow on object with zero length");
1374       return;
1375     }
1376     if (aht.solid && out->supports_filled_polygons()) {
1377       base *= aht.height / hyp;
1378       draw_arrow(strt, strt - v[0], aht, lt,
1379                  graphic_object::get_outline_color());
1380       sp = strt + base*0.1; // to reserve spline shape
1381     } else {
1382       base *= fabs(lt.thickness) / hyp / 72 / 4;
1383       sp = strt + base;
1384       draw_arrow(sp, sp - v[0], aht, lt,
1385                  graphic_object::get_outline_color());
1386     }
1387   }
1388   if (arrow_at_end) {
1389     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1390     double hyp = hypot(base);
1391     if (hyp == 0.0) {
1392       error("cannot draw arrow on object with zero length");
1393       return;
1394     }
1395     if (aht.solid && out->supports_filled_polygons()) {
1396       base *= aht.height / hyp;
1397       draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1398                  graphic_object::get_outline_color());
1399       v[n-1] = en - base*0.1; // to reserve spline shape
1400     } else {
1401       base *= fabs(lt.thickness) / hyp / 72 / 4;
1402       v[n-1] = en - base;
1403       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1404                  graphic_object::get_outline_color());
1405     }
1406   }
1407   out->spline(sp, v, n, lt);
1408   out->reset_color();
1409 }
1410
1411 line_object::~line_object()
1412 {
1413   a_delete v;
1414 }
1415
1416 linear_object *object_spec::make_line(position *curpos, direction *dirp)
1417 {
1418   static position last_line;
1419   static int have_last_line = 0;
1420   *dirp = dir;
1421   // No need to look at at since `at' attribute sets `from' attribute.
1422   position startpos = (flags & HAS_FROM) ? from : *curpos;
1423   if (!(flags & HAS_SEGMENT)) {
1424     if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
1425         && have_last_line)
1426       segment_pos = last_line;
1427     else 
1428       switch (dir) {
1429       case UP_DIRECTION:
1430         segment_pos.y = segment_height;
1431         break;
1432       case DOWN_DIRECTION:
1433         segment_pos.y = -segment_height;
1434         break;
1435       case LEFT_DIRECTION:
1436         segment_pos.x = -segment_width;
1437         break;
1438       case RIGHT_DIRECTION:
1439         segment_pos.x = segment_width;
1440         break;
1441       default:
1442         assert(0);
1443       }
1444   }
1445   segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1446   // reverse the segment_list so that it's in forward order
1447   segment *old = segment_list;
1448   segment_list = 0;
1449   while (old != 0) {
1450     segment *tem = old->next;
1451     old->next = segment_list;
1452     segment_list = old;
1453     old = tem;
1454   }
1455   // Absolutise all movements
1456   position endpos = startpos;
1457   int nsegments = 0;
1458   segment *s;
1459   for (s = segment_list; s; s = s->next, nsegments++)
1460     if (s->is_absolute)
1461       endpos = s->pos;
1462     else {
1463       endpos += s->pos;
1464       s->pos = endpos;
1465       s->is_absolute = 1;       // to avoid confusion
1466     }
1467   // handle chop
1468   line_object *p = 0;
1469   position *v = new position[nsegments];
1470   int i = 0;
1471   for (s = segment_list; s; s = s->next, i++)
1472     v[i] = s->pos;
1473   if (flags & IS_DEFAULT_CHOPPED) {
1474     lookup_variable("circlerad", &start_chop);
1475     end_chop = start_chop;
1476     flags |= IS_CHOPPED;
1477   }
1478   if (flags & IS_CHOPPED) {
1479     position start_chop_vec, end_chop_vec;
1480     if (start_chop != 0.0) {
1481       start_chop_vec = v[0] - startpos;
1482       start_chop_vec *= start_chop / hypot(start_chop_vec);
1483     }
1484     if (end_chop != 0.0) {
1485       end_chop_vec = (v[nsegments - 1]
1486                       - (nsegments > 1 ? v[nsegments - 2] : startpos));
1487       end_chop_vec *= end_chop / hypot(end_chop_vec);
1488     }
1489     startpos += start_chop_vec;
1490     v[nsegments - 1] -= end_chop_vec;
1491     endpos -= end_chop_vec;
1492   }
1493   switch (type) {
1494   case SPLINE_OBJECT:
1495     p = new spline_object(startpos, endpos, v, nsegments);
1496     break;
1497   case ARROW_OBJECT:
1498     p = new arrow_object(startpos, endpos, v, nsegments);
1499     break;
1500   case LINE_OBJECT:
1501     p = new line_object(startpos, endpos, v, nsegments);
1502     break;
1503   default:
1504     assert(0);
1505   }
1506   have_last_line = 1;
1507   last_line = endpos - startpos;
1508   *curpos = endpos;
1509   return p;
1510 }
1511
1512 class arc_object : public linear_object {
1513   int clockwise;
1514   position cent;
1515   double rad;
1516 public:
1517   arc_object(int, const position &, const position &, const position &);
1518   position origin() { return cent; }
1519   position center() { return cent; }
1520   double radius() { return rad; }
1521   position north();
1522   position south();
1523   position east();
1524   position west();
1525   position north_east();
1526   position north_west();
1527   position south_east();
1528   position south_west();
1529   void update_bounding_box(bounding_box *);
1530   object_type type() { return ARC_OBJECT; }
1531   void print();
1532   void move_by(const position &pos);
1533 };
1534
1535 arc_object::arc_object(int cw, const position &s, const position &e,
1536                        const position &c)
1537 : linear_object(s, e), clockwise(cw), cent(c)
1538 {
1539   rad = hypot(c - s);
1540 }
1541
1542 void arc_object::move_by(const position &pos)
1543 {
1544   linear_object::move_by(pos);
1545   cent += pos;
1546 }
1547
1548 // we get arc corners from the corresponding circle
1549
1550 position arc_object::north()
1551 {
1552   position result(cent);
1553   result.y += rad;
1554   return result;
1555 }
1556
1557 position arc_object::south()
1558 {
1559   position result(cent);
1560   result.y -= rad;
1561   return result;
1562 }
1563
1564 position arc_object::east()
1565 {
1566   position result(cent);
1567   result.x += rad;
1568   return result;
1569 }
1570
1571 position arc_object::west()
1572 {
1573   position result(cent);
1574   result.x -= rad;
1575   return result;
1576 }
1577
1578 position arc_object::north_east()
1579 {
1580   position result(cent);
1581   result.x += rad/M_SQRT2;
1582   result.y += rad/M_SQRT2;
1583   return result;
1584 }
1585
1586 position arc_object::north_west()
1587 {
1588   position result(cent);
1589   result.x -= rad/M_SQRT2;
1590   result.y += rad/M_SQRT2;
1591   return result;
1592 }
1593
1594 position arc_object::south_east()
1595 {
1596   position result(cent);
1597   result.x += rad/M_SQRT2;
1598   result.y -= rad/M_SQRT2;
1599   return result;
1600 }
1601
1602 position arc_object::south_west()
1603 {
1604   position result(cent);
1605   result.x -= rad/M_SQRT2;
1606   result.y -= rad/M_SQRT2;
1607   return result;
1608 }
1609
1610
1611 void arc_object::print()
1612 {
1613   if (lt.type == line_type::invisible)
1614     return;
1615   out->set_color(0, graphic_object::get_outline_color());
1616   // handle arrow direction; make shorter line for arc
1617   position sp, ep, b;
1618   if (clockwise) {
1619     sp = en;
1620     ep = strt;
1621   } else {
1622     sp = strt;
1623     ep = en;
1624   }
1625   if (arrow_at_start) {
1626     double theta = aht.height / rad;
1627     if (clockwise)
1628       theta = - theta;
1629     b = strt - cent;
1630     b = position(b.x*cos(theta) - b.y*sin(theta),
1631                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1632     if (clockwise)
1633       ep = b;
1634     else
1635       sp = b;
1636     if (aht.solid && out->supports_filled_polygons()) {
1637       draw_arrow(strt, strt - b, aht, lt,
1638                  graphic_object::get_outline_color());
1639     } else {
1640       position v = b;
1641       theta = fabs(lt.thickness) / 72 / 4 / rad;
1642       if (clockwise)
1643         theta = - theta;
1644       b = strt - cent;
1645       b = position(b.x*cos(theta) - b.y*sin(theta),
1646                    b.x*sin(theta) + b.y*cos(theta)) + cent;
1647       draw_arrow(b, b - v, aht, lt,
1648                  graphic_object::get_outline_color());
1649       out->line(b, &v, 1, lt);
1650     }
1651   }
1652   if (arrow_at_end) {
1653     double theta = aht.height / rad;
1654     if (!clockwise)
1655       theta = - theta;
1656     b = en - cent;
1657     b = position(b.x*cos(theta) - b.y*sin(theta),
1658                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1659     if (clockwise)
1660       sp = b;
1661     else
1662       ep = b;
1663     if (aht.solid && out->supports_filled_polygons()) {
1664       draw_arrow(en, en - b, aht, lt,
1665                  graphic_object::get_outline_color());
1666     } else {
1667       position v = b;
1668       theta = fabs(lt.thickness) / 72 / 4 / rad;
1669       if (!clockwise)
1670         theta = - theta;
1671       b = en - cent;
1672       b = position(b.x*cos(theta) - b.y*sin(theta),
1673                    b.x*sin(theta) + b.y*cos(theta)) + cent;
1674       draw_arrow(b, b - v, aht, lt,
1675                  graphic_object::get_outline_color());
1676       out->line(b, &v, 1, lt);
1677     }
1678   }
1679   out->arc(sp, cent, ep, lt);
1680   out->reset_color();
1681 }
1682
1683 inline double max(double a, double b)
1684 {
1685   return a > b ? a : b;
1686 }
1687
1688 void arc_object::update_bounding_box(bounding_box *p)
1689 {
1690   p->encompass(strt);
1691   p->encompass(en);
1692   position start_offset = strt - cent;
1693   if (start_offset.x == 0.0 && start_offset.y == 0.0)
1694     return;
1695   position end_offset = en  - cent;
1696   if (end_offset.x == 0.0 && end_offset.y == 0.0)
1697     return;
1698   double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0);
1699   double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0);
1700   if (clockwise) {
1701     double temp = start_quad;
1702     start_quad = end_quad;
1703     end_quad = temp;
1704   }
1705   if (start_quad < 0.0)
1706     start_quad += 4.0;
1707   while (end_quad <= start_quad)
1708     end_quad += 4.0;
1709   double r = max(hypot(start_offset), hypot(end_offset));
1710   for (int q = int(start_quad) + 1; q < end_quad; q++) {
1711     position offset;
1712     switch (q % 4) {
1713     case 0:
1714       offset.x = r;
1715       break;
1716     case 1:
1717       offset.y = r;
1718       break;
1719     case 2:
1720       offset.x = -r;
1721       break;
1722     case 3:
1723       offset.y = -r;
1724       break;
1725     }
1726     p->encompass(cent + offset);
1727   }
1728 }
1729
1730 // We ignore the with attribute. The at attribute always refers to the center.
1731
1732 linear_object *object_spec::make_arc(position *curpos, direction *dirp)
1733 {
1734   *dirp = dir;
1735   int cw = (flags & IS_CLOCKWISE) != 0;
1736   // compute the start
1737   position startpos;
1738   if (flags & HAS_FROM)
1739     startpos = from;
1740   else
1741     startpos = *curpos;
1742   if (!(flags & HAS_RADIUS))
1743     lookup_variable("arcrad", &radius);
1744   // compute the end
1745   position endpos;
1746   if (flags & HAS_TO)
1747     endpos = to;
1748   else {
1749     position m(radius, radius);
1750     // Adjust the signs.
1751     if (cw) {
1752       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1753         m.x = -m.x;
1754       if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
1755         m.y = -m.y;
1756       *dirp = direction((dir + 3) % 4);
1757     }
1758     else {
1759       if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
1760         m.x = -m.x;
1761       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1762         m.y = -m.y;
1763       *dirp = direction((dir + 1) % 4);
1764     }
1765     endpos = startpos + m;
1766   }
1767   // compute the center
1768   position centerpos;
1769   if (flags & HAS_AT)
1770     centerpos = at;
1771   else if (startpos == endpos)
1772     centerpos = startpos;
1773   else {
1774     position h = (endpos - startpos)/2.0;
1775     double d = hypot(h);
1776     if (radius <= 0)
1777       radius = .25;
1778     // make the radius big enough
1779     while (radius < d)
1780       radius *= 2.0;
1781     double alpha = acos(d/radius);
1782     double theta = atan2(h.y, h.x);
1783     if (cw)
1784       theta -= alpha;
1785     else
1786       theta += alpha;
1787     centerpos = position(cos(theta), sin(theta))*radius + startpos;
1788   }
1789   arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
1790   *curpos = endpos;
1791   return p;
1792 }
1793
1794 graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
1795 {
1796   linear_object *obj;
1797   if (type == ARC_OBJECT)
1798     obj = make_arc(curpos, dirp);
1799   else
1800     obj = make_line(curpos, dirp);
1801   if (type == ARROW_OBJECT
1802       && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0)
1803     flags |= HAS_RIGHT_ARROW_HEAD;
1804   if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) {
1805     arrow_head_type a;
1806     int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0;
1807     int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0;
1808     if (flags & HAS_HEIGHT)
1809       a.height = height;
1810     else
1811       lookup_variable("arrowht", &a.height);
1812     if (flags & HAS_WIDTH)
1813       a.width = width;
1814     else
1815       lookup_variable("arrowwid", &a.width);
1816     double solid;
1817     lookup_variable("arrowhead", &solid);
1818     a.solid = solid != 0.0;
1819     obj->add_arrows(at_start, at_end, a);
1820   }
1821   return obj;
1822 }
1823
1824 object *object_spec::make_object(position *curpos, direction *dirp)
1825 {
1826   graphic_object *obj = 0;
1827   switch (type) {
1828   case BLOCK_OBJECT:
1829     obj = make_block(curpos, dirp);
1830     break;
1831   case BOX_OBJECT:
1832     obj = make_box(curpos, dirp);
1833     break;
1834   case TEXT_OBJECT:
1835     obj = make_text(curpos, dirp);
1836     break;
1837   case ELLIPSE_OBJECT:
1838     obj = make_ellipse(curpos, dirp);
1839     break;
1840   case CIRCLE_OBJECT:
1841     obj = make_circle(curpos, dirp);
1842     break;
1843   case MOVE_OBJECT:
1844     obj = make_move(curpos, dirp);
1845     break;
1846   case ARC_OBJECT:
1847   case LINE_OBJECT:
1848   case SPLINE_OBJECT:
1849   case ARROW_OBJECT:
1850     obj = make_linear(curpos, dirp);
1851     break;
1852   case MARK_OBJECT:
1853   case OTHER_OBJECT:
1854   default:
1855     assert(0);
1856     break;
1857   }
1858   if (obj) {
1859     if (flags & IS_INVISIBLE)
1860       obj->set_invisible();
1861     if (text != 0)
1862       obj->add_text(text, (flags & IS_ALIGNED) != 0);
1863     if (flags & IS_DOTTED)
1864       obj->set_dotted(dash_width);
1865     else if (flags & IS_DASHED)
1866       obj->set_dashed(dash_width);
1867     double th;
1868     if (flags & HAS_THICKNESS)
1869       th = thickness;
1870     else
1871       lookup_variable("linethick", &th);
1872     obj->set_thickness(th);
1873     if (flags & IS_OUTLINED)
1874       obj->set_outline_color(outlined);
1875     if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) {
1876       if (flags & IS_SHADED)
1877         obj->set_fill_color(shaded);
1878       else {
1879         if (flags & IS_DEFAULT_FILLED)
1880           lookup_variable("fillval", &fill);
1881         if (fill < 0.0)
1882           error("bad fill value %1", fill);
1883         else
1884           obj->set_fill(fill);
1885       }
1886     }
1887   }
1888   return obj;
1889 }
1890
1891 struct string_list {
1892   string_list *next;
1893   char *str;
1894   string_list(char *);
1895   ~string_list();
1896 };
1897
1898 string_list::string_list(char *s)
1899 : next(0), str(s)
1900 {
1901 }
1902
1903 string_list::~string_list()
1904 {
1905   a_delete str;
1906 }
1907   
1908 /* A path is used to hold the argument to the `with' attribute.  For
1909    example, `.nw' or `.A.s' or `.A'.  The major operation on a path is to
1910    take a place and follow the path through the place to place within the
1911    place.  Note that `.A.B.C.sw' will work.
1912
1913    For compatibility with DWB pic, `with' accepts positions also (this
1914    is incorrectly documented in CSTR 116). */
1915
1916 path::path(corner c)
1917 : crn(c), label_list(0), ypath(0), is_position(0)
1918 {
1919 }
1920
1921 path::path(position p)
1922 : crn(0), label_list(0), ypath(0), is_position(1)
1923 {
1924   pos.x = p.x;
1925   pos.y = p.y;
1926 }
1927
1928 path::path(char *l, corner c)
1929 : crn(c), ypath(0), is_position(0)
1930 {
1931   label_list = new string_list(l);
1932 }
1933
1934 path::~path()
1935 {
1936   while (label_list) {
1937     string_list *tem = label_list;
1938     label_list = label_list->next;
1939     delete tem;
1940   }
1941   delete ypath;
1942 }
1943
1944 void path::append(corner c)
1945 {
1946   assert(crn == 0);
1947   crn = c;
1948 }
1949
1950 void path::append(char *s)
1951 {
1952   string_list **p;
1953   for (p = &label_list; *p; p = &(*p)->next)
1954     ;
1955   *p = new string_list(s);
1956 }
1957
1958 void path::set_ypath(path *p)
1959 {
1960   ypath = p;
1961 }
1962
1963 // return non-zero for success
1964
1965 int path::follow(const place &pl, place *result) const
1966 {
1967   if (is_position) {
1968     result->x = pos.x;
1969     result->y = pos.y;
1970     result->obj = 0;
1971     return 1;
1972   }
1973   const place *p = &pl;
1974   for (string_list *lb = label_list; lb; lb = lb->next)
1975     if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) {
1976       lex_error("object does not contain a place `%1'", lb->str);
1977       return 0;
1978     }
1979   if (crn == 0 || p->obj == 0)
1980     *result = *p;
1981   else {
1982     position ps = ((p->obj)->*(crn))();
1983     result->x = ps.x;
1984     result->y = ps.y;
1985     result->obj = 0;
1986   }
1987   if (ypath) {
1988     place tem;
1989     if (!ypath->follow(pl, &tem))
1990       return 0;
1991     result->y = tem.y;
1992     if (result->obj != tem.obj)
1993       result->obj = 0;
1994   }
1995   return 1;
1996 }
1997
1998 void print_object_list(object *p)
1999 {
2000   for (; p; p = p->next) {
2001     p->print();
2002     p->print_text();
2003   }
2004 }
2005
2006 void print_picture(object *obj)
2007 {
2008   bounding_box bb;
2009   for (object *p = obj; p; p = p->next)
2010     p->update_bounding_box(&bb);
2011   double scale;
2012   lookup_variable("scale", &scale);
2013   out->start_picture(scale, bb.ll, bb.ur);
2014   print_object_list(obj);
2015   out->finish_picture();
2016 }
2017