Initial import of binutils 2.22 on the new vendor branch
[dragonfly.git] / contrib / groff / src / preproc / pic / object.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005,
3                  2006, 2007, 2009
4      Free Software Foundation, Inc.
5      Written by James Clark (jjc@jclark.com)
6
7 This file is part of groff.
8
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
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   xslanted = 0;
416   yslanted = 0;
417   outlined = 0;
418   with = 0;
419   dir = RIGHT_DIRECTION;
420 }
421
422 object_spec::~object_spec()
423 {
424   delete tbl;
425   while (segment_list != 0) {
426     segment *tem = segment_list;
427     segment_list = segment_list->next;
428     delete tem;
429   }
430   object *p = oblist.head;
431   while (p != 0) {
432     object *tem = p;
433     p = p->next;
434     delete tem;
435   }
436   while (text != 0) {
437     text_item *tem = text;
438     text = text->next;
439     delete tem;
440   }
441   delete with;
442   a_delete shaded;
443   a_delete outlined;
444 }
445
446 class command_object : public object {
447   char *s;
448   const char *filename;
449   int lineno;
450 public:
451   command_object(char *, const char *, int);
452   ~command_object();
453   object_type type() { return OTHER_OBJECT; }
454   void print();
455 };
456
457 command_object::command_object(char *p, const char *fn, int ln)
458 : s(p), filename(fn), lineno(ln)
459 {
460 }
461
462 command_object::~command_object()
463 {
464   a_delete s;
465 }
466
467 void command_object::print()
468 {
469   out->command(s, filename, lineno);
470 }
471
472 object *make_command_object(char *s, const char *fn, int ln)
473 {
474   return new command_object(s, fn, ln);
475 }
476
477 class mark_object : public object {
478 public:
479   mark_object();
480   object_type type();
481 };
482
483 object *make_mark_object()
484 {
485   return new mark_object();
486 }
487
488 mark_object::mark_object()
489 {
490 }
491
492 object_type mark_object::type()
493 {
494   return MARK_OBJECT;
495 }
496
497 object_list::object_list() : head(0), tail(0)
498 {
499 }
500
501 void object_list::append(object *obj)
502 {
503   if (tail == 0) {
504     obj->next = obj->prev = 0;
505     head = tail = obj;
506   }
507   else {
508     obj->prev = tail;
509     obj->next = 0;
510     tail->next = obj;
511     tail = obj;
512   }
513 }
514
515 void object_list::wrap_up_block(object_list *ol)
516 {
517   object *p;
518   for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev)
519     ;
520   assert(p != 0);
521   ol->head = p->next;
522   if (ol->head) {
523     ol->tail = tail;
524     ol->head->prev = 0;
525   }
526   else
527     ol->tail = 0;
528   tail = p->prev;
529   if (tail)
530     tail->next = 0;
531   else
532     head = 0;
533   delete p;
534 }
535
536 text_piece::text_piece()
537 : text(0), filename(0), lineno(-1)
538 {
539   adj.h = CENTER_ADJUST;
540   adj.v = NONE_ADJUST;
541 }
542
543 text_piece::~text_piece()
544 {
545   a_delete text;
546 }
547
548 class graphic_object : public object {
549   int ntext;
550   text_piece *text;
551   int aligned;
552 protected:
553   line_type lt;
554   char *outline_color;
555   char *color_fill;
556 public:
557   graphic_object();
558   ~graphic_object();
559   object_type type() = 0;
560   void print_text();
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 *);
572 };
573
574 graphic_object::graphic_object()
575 : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
576 {
577 }
578
579 void graphic_object::set_dotted(double wid)
580 {
581   lt.type = line_type::dotted;
582   lt.dash_width = wid;
583 }
584
585 void graphic_object::set_dashed(double wid)
586 {
587   lt.type = line_type::dashed;
588   lt.dash_width = wid;
589 }
590
591 void graphic_object::set_thickness(double th)
592 {
593   lt.thickness = th;
594 }
595
596 void graphic_object::set_fill(double)
597 {
598 }
599
600 void graphic_object::set_xslanted(double)
601 {
602 }
603
604 void graphic_object::set_yslanted(double)
605 {
606 }
607
608 void graphic_object::set_fill_color(char *c)
609 {
610   color_fill = strsave(c);
611 }
612
613 void graphic_object::set_outline_color(char *c)
614 {
615   outline_color = strsave(c);
616 }
617
618 char *graphic_object::get_outline_color()
619 {
620   return outline_color;
621 }
622
623 void graphic_object::set_invisible()
624 {
625   lt.type = line_type::invisible;
626 }
627
628 void graphic_object::add_text(text_item *t, int a)
629 {
630   aligned = a;
631   int len = 0;
632   text_item *p;
633   for (p = t; p; p = p->next)
634     len++;
635   if (len == 0)
636     text = 0;
637   else {
638     text = new text_piece[len];
639     for (p = t, len = 0; p; p = p->next, len++) {
640       text[len].text = p->text;
641       p->text = 0;
642       text[len].adj = p->adj;
643       text[len].filename = p->filename;
644       text[len].lineno = p->lineno;
645     }
646   }
647   ntext = len;
648 }
649
650 void graphic_object::print_text()
651 {
652   double angle = 0.0;
653   if (aligned) {
654     position d(end() - start());
655     if (d.x != 0.0 || d.y != 0.0)
656       angle = atan2(d.y, d.x);
657   }
658   if (text != 0) {
659     out->set_color(color_fill, get_outline_color());
660     out->text(center(), text, ntext, angle);
661     out->reset_color();
662   }
663 }
664
665 graphic_object::~graphic_object()
666 {
667   if (text)
668     ad_delete(ntext) text;
669 }
670
671 class rectangle_object : public graphic_object {
672 protected:
673   position cent;
674   position dim;
675 public:
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 &);
692 };
693
694 rectangle_object::rectangle_object(const position &d)
695 : dim(d)
696 {
697 }
698
699 void rectangle_object::update_bounding_box(bounding_box *p)
700 {
701   p->encompass(cent - dim/2.0);
702   p->encompass(cent + dim/2.0);
703 }
704
705 void rectangle_object::move_by(const position &a)
706 {
707   cent += a;
708 }
709
710 class closed_object : public rectangle_object {
711 public:
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);
718 protected:
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
723 };
724
725 closed_object::closed_object(const position &pos)
726 : rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
727 {
728 }
729
730 void closed_object::set_fill(double f)
731 {
732   assert(f >= 0.0);
733   fill = f;
734 }
735
736 /* accept positive and negative values */ 
737 void closed_object::set_xslanted(double s)
738 {
739   //assert(s >= 0.0);
740   xslanted = s;
741 }
742 /* accept positive and negative values */ 
743 void closed_object::set_yslanted(double s)
744 {
745   //assert(s >= 0.0);
746   yslanted = s;
747 }
748
749 void closed_object::set_fill_color(char *f)
750 {
751   color_fill = strsave(f);
752 }
753
754 class box_object : public closed_object {
755   double xrad;
756   double yrad;
757 public:
758   box_object(const position &, double);
759   object_type type() { return BOX_OBJECT; }
760   void print();
761   position north_east();
762   position north_west();
763   position south_east();
764   position south_west();
765 };
766
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)
769 {
770 }
771
772 const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
773
774 position box_object::north_east()
775 {
776   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
777                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
778 }
779
780 position box_object::north_west()
781 {
782   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
783                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
784 }
785
786 position box_object::south_east()
787 {
788   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
789                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
790 }
791
792 position box_object::south_west()
793 {
794   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
795                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
796 }
797
798 void box_object::print()
799 {
800   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
801     return;
802   out->set_color(color_fill, graphic_object::get_outline_color());
803   if (xrad == 0.0) {
804     distance dim2 = dim/2.0;
805     position vec[4];
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);
813   }
814   else {
815     distance abs_dim(fabs(dim.x), fabs(dim.y));
816     out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill);
817   }
818   out->reset_color();
819 }
820
821 graphic_object *object_spec::make_box(position *curpos, direction *dirp)
822 {
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;
830     else
831       lookup_variable("boxht", &height);
832   }
833   if (!(flags & HAS_WIDTH)) {
834     if ((flags & IS_SAME) && have_last_box)
835       width = last_box_width;
836     else
837       lookup_variable("boxwid", &width);
838   }
839   if (!(flags & HAS_RADIUS)) {
840     if ((flags & IS_SAME) && have_last_box)
841       radius = last_box_radius;
842     else
843       lookup_variable("boxrad", &radius);
844   }
845   last_box_width = width;
846   last_box_height = height;
847   last_box_radius = radius;
848   have_last_box = 1;
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)) {
856     delete p;
857     p = 0;
858   }
859   return p;
860 }
861
862 // return non-zero for success
863
864 int object_spec::position_rectangle(rectangle_object *p,
865                                     position *curpos, direction *dirp)
866 {
867   position pos;
868   dir = *dirp;                  // ignore any direction in attribute list
869   position motion;
870   switch (dir) {
871   case UP_DIRECTION:
872     motion.y = p->height()/2.0;
873     break;
874   case DOWN_DIRECTION:
875     motion.y = -p->height()/2.0;
876     break;
877   case LEFT_DIRECTION:
878     motion.x = -p->width()/2.0;
879     break;
880   case RIGHT_DIRECTION:
881     motion.x = p->width()/2.0;
882     break;
883   default:
884     assert(0);
885   }
886   if (flags & HAS_AT) {
887     pos = at;
888     if (flags & HAS_WITH) {
889       place offset;
890       place here;
891       here.obj = p;
892       if (!with->follow(here, &offset))
893         return 0;
894       pos -= offset;
895     }
896   }
897   else {
898     pos = *curpos;
899     pos += motion;
900   }
901   p->move_by(pos);
902   pos += motion;
903   *curpos = pos;
904   return 1;
905 }
906
907 class block_object : public rectangle_object {
908   object_list oblist;
909   PTABLE(place) *tbl;
910 public:
911   block_object(const position &, const object_list &ol, PTABLE(place) *t);
912   ~block_object();
913   place *find_label(const char *);
914   object_type type();
915   void move_by(const position &);
916   void print();
917 };
918
919 block_object::block_object(const position &d, const object_list &ol,
920                            PTABLE(place) *t)
921 : rectangle_object(d), oblist(ol), tbl(t)
922 {
923 }
924
925 block_object::~block_object()
926 {
927   delete tbl;
928   object *p = oblist.head;
929   while (p != 0) {
930     object *tem = p;
931     p = p->next;
932     delete tem;
933   }
934 }
935
936 void block_object::print()
937 {
938   out->begin_block(south_west(), north_east());
939   print_object_list(oblist.head);
940   out->end_block();
941 }
942
943 static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
944 {
945   // Adjust all the labels that aren't attached to objects.
946   PTABLE_ITERATOR(place) iter(tbl);
947   const char *key;
948   place *pl;
949   while (iter.next(&key, &pl))
950     if (key && csupper(key[0]) && pl->obj == 0) {
951       pl->x += a.x;
952       pl->y += a.y;
953     }
954 }
955
956 void block_object::move_by(const position &a)
957 {
958   cent += a;
959   for (object *p = oblist.head; p; p = p->next)
960     p->move_by(a);
961   adjust_objectless_places(tbl, a);
962 }
963
964
965 place *block_object::find_label(const char *name)
966 {
967   return tbl->lookup(name);
968 }
969
970 object_type block_object::type()
971 {
972   return BLOCK_OBJECT;
973 }
974
975 graphic_object *object_spec::make_block(position *curpos, direction *dirp)
976 {
977   bounding_box bb;
978   for (object *p = oblist.head; p; p = p->next)
979     p->update_bounding_box(&bb);
980   position dim;
981   if (!bb.blank) {
982     position m = -(bb.ll + bb.ur)/2.0;
983     for (object *p = oblist.head; p; p = p->next)
984       p->move_by(m);
985     adjust_objectless_places(tbl, m);
986     dim = bb.ur - bb.ll;
987   }
988   if (flags & HAS_WIDTH)
989     dim.x = width;
990   if (flags & HAS_HEIGHT)
991     dim.y = height;
992   block_object *block = new block_object(dim, oblist, tbl);
993   if (!position_rectangle(block, curpos, dirp)) {
994     delete block;
995     block = 0;
996   }
997   tbl = 0;
998   oblist.head = oblist.tail = 0;
999   return block;
1000 }
1001
1002 class text_object : public rectangle_object {
1003 public:
1004   text_object(const position &);
1005   object_type type() { return TEXT_OBJECT; }
1006 };
1007
1008 text_object::text_object(const position &d)
1009 : rectangle_object(d)
1010 {
1011 }
1012
1013 graphic_object *object_spec::make_text(position *curpos, direction *dirp)
1014 {
1015   if (!(flags & HAS_HEIGHT)) {
1016     lookup_variable("textht", &height);
1017     int nitems = 0;
1018     for (text_item *t = text; t; t = t->next)
1019       nitems++;
1020     height *= nitems;
1021   }
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)) {
1026     delete p;
1027     p = 0;
1028   }
1029   return p;
1030 }
1031
1032
1033 class ellipse_object : public closed_object {
1034 public:
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; }
1046   void print();
1047 };
1048
1049 ellipse_object::ellipse_object(const position &d)
1050 : closed_object(d)
1051 {
1052 }
1053
1054 void ellipse_object::print()
1055 {
1056   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1057     return;
1058   out->set_color(color_fill, graphic_object::get_outline_color());
1059   out->ellipse(cent, dim, lt, fill);
1060   out->reset_color();
1061 }
1062
1063 graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
1064 {
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;
1071     else
1072       lookup_variable("ellipseht", &height);
1073   }
1074   if (!(flags & HAS_WIDTH)) {
1075     if ((flags & IS_SAME) && have_last_ellipse)
1076       width = last_ellipse_width;
1077     else
1078       lookup_variable("ellipsewid", &width);
1079   }
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)) {
1085     delete p;
1086     return 0;
1087   }
1088   return p;
1089 }
1090
1091 class circle_object : public ellipse_object {
1092 public:
1093   circle_object(double);
1094   object_type type() { return CIRCLE_OBJECT; }
1095   void print();
1096 };
1097
1098 circle_object::circle_object(double diam)
1099 : ellipse_object(position(diam, diam))
1100 {
1101 }
1102
1103 void circle_object::print()
1104 {
1105   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1106     return;
1107   out->set_color(color_fill, graphic_object::get_outline_color());
1108   out->circle(cent, dim.x/2.0, lt, fill);
1109   out->reset_color();
1110 }
1111
1112 graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
1113 {
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;
1119     else
1120       lookup_variable("circlerad", &radius);
1121   }
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)) {
1126     delete p;
1127     return 0;
1128   }
1129   return p;
1130 }
1131
1132 class move_object : public graphic_object {
1133   position strt;
1134   position en;
1135 public:
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 &);
1141 };
1142
1143 move_object::move_object(const position &s, const position &e)
1144 : strt(s), en(e)
1145 {
1146 }
1147
1148 void move_object::update_bounding_box(bounding_box *p)
1149 {
1150   p->encompass(strt);
1151   p->encompass(en);
1152 }
1153
1154 void move_object::move_by(const position &a)
1155 {
1156   strt += a;
1157   en += a;
1158 }
1159
1160 graphic_object *object_spec::make_move(position *curpos, direction *dirp)
1161 {
1162   static position last_move;
1163   static int have_last_move = 0;
1164   *dirp = dir;
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;
1170     else {
1171       switch (dir) {
1172       case UP_DIRECTION:
1173         segment_pos.y = segment_height;
1174         break;
1175       case DOWN_DIRECTION:
1176         segment_pos.y = -segment_height;
1177         break;
1178       case LEFT_DIRECTION:
1179         segment_pos.x = -segment_width;
1180         break;
1181       case RIGHT_DIRECTION:
1182         segment_pos.x = segment_width;
1183         break;
1184       default:
1185         assert(0);
1186       }
1187     }
1188   }
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;
1192   segment_list = 0;
1193   while (old != 0) {
1194     segment *tem = old->next;
1195     old->next = segment_list;
1196     segment_list = old;
1197     old = tem;
1198   }
1199   // Compute the end position.
1200   position endpos = startpos;
1201   for (segment *s = segment_list; s; s = s->next)
1202     if (s->is_absolute)
1203       endpos = s->pos;
1204     else 
1205       endpos += s->pos;
1206   have_last_move = 1;
1207   last_move = endpos - startpos;
1208   move_object *p = new move_object(startpos, endpos);
1209   *curpos = endpos;
1210   return p;
1211 }
1212
1213 class linear_object : public graphic_object {
1214 protected:
1215   char arrow_at_start;
1216   char arrow_at_end;
1217   arrow_head_type aht;
1218   position strt;
1219   position en;
1220 public:
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 &);
1228 };
1229
1230 class line_object : public linear_object {
1231 protected:
1232   position *v;
1233   int n;
1234 public:
1235   line_object(const position &s, const position &e, position *, int);
1236   ~line_object();
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 *);
1245   void print();
1246   void move_by(const position &);
1247 };
1248
1249 class arrow_object : public line_object {
1250 public:
1251   arrow_object(const position &, const position &, position *, int);
1252   object_type type() { return ARROW_OBJECT; }
1253 };
1254
1255 class spline_object : public line_object {
1256 public:
1257   spline_object(const position &, const position &, position *, int);
1258   object_type type() { return SPLINE_OBJECT; }
1259   void print();
1260   void update_bounding_box(bounding_box *);
1261 };
1262
1263 linear_object::linear_object(const position &s, const position &e)
1264 : arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
1265 {
1266 }
1267
1268 void linear_object::move_by(const position &a)
1269 {
1270   strt += a;
1271   en += a;
1272 }
1273
1274 void linear_object::add_arrows(int at_start, int at_end,
1275                                const arrow_head_type &a)
1276 {
1277   arrow_at_start = at_start;
1278   arrow_at_end = at_end;
1279   aht = a;
1280 }
1281
1282 line_object::line_object(const position &s, const position &e,
1283                          position *p, int i)
1284 : linear_object(s, e), v(p), n(i)
1285 {
1286 }
1287
1288 void line_object::print()
1289 {
1290   if (lt.type == line_type::invisible)
1291     return;
1292   out->set_color(0, graphic_object::get_outline_color());
1293   // shorten line length to avoid arrow sticking.
1294   position sp = strt;
1295   if (arrow_at_start) {
1296     position base = v[0] - strt;
1297     double hyp = hypot(base);
1298     if (hyp == 0.0) {
1299       error("cannot draw arrow on object with zero length");
1300       return;
1301     }
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());
1306       sp = strt + base;
1307     } else {
1308       base *= fabs(lt.thickness) / hyp / 72 / 4;
1309       sp = strt + base;
1310       draw_arrow(sp, sp - v[0], aht, lt,
1311                  graphic_object::get_outline_color());
1312     }
1313   }
1314   if (arrow_at_end) {
1315     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1316     double hyp = hypot(base);
1317     if (hyp == 0.0) {
1318       error("cannot draw arrow on object with zero length");
1319       return;
1320     }
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());
1325       v[n-1] = en - base;
1326     } else {
1327       base *= fabs(lt.thickness) / hyp / 72 / 4;
1328       v[n-1] = en - base;
1329       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1330                  graphic_object::get_outline_color());
1331     }
1332   }
1333   out->line(sp, v, n, lt);
1334   out->reset_color();
1335 }
1336
1337 void line_object::update_bounding_box(bounding_box *p)
1338 {
1339   p->encompass(strt);
1340   for (int i = 0; i < n; i++)
1341     p->encompass(v[i]);
1342 }
1343
1344 void line_object::move_by(const position &pos)
1345 {
1346   linear_object::move_by(pos);
1347   for (int i = 0; i < n; i++)
1348     v[i] += pos;
1349 }
1350   
1351 void spline_object::update_bounding_box(bounding_box *p)
1352 {
1353   p->encompass(strt);
1354   p->encompass(en);
1355   /*
1356
1357   If
1358
1359   p1 = q1/2 + q2/2
1360   p2 = q1/6 + q2*5/6
1361   p3 = q2*5/6 + q3/6
1362   p4 = q2/2 + q3/2
1363   [ the points for the Bezier cubic ]
1364
1365   and
1366
1367   t = .5
1368
1369   then
1370
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 ]
1373
1374   = .125*q1 + .75*q2 + .125*q3
1375
1376   */
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);
1379 }
1380
1381 arrow_object::arrow_object(const position &s, const position &e,
1382                            position *p, int i)
1383 : line_object(s, e, p, i)
1384 {
1385 }
1386
1387 spline_object::spline_object(const position &s, const position &e,
1388                              position *p, int i)
1389 : line_object(s, e, p, i)
1390 {
1391 }
1392
1393 void spline_object::print()
1394 {
1395   if (lt.type == line_type::invisible)
1396     return;
1397   out->set_color(0, graphic_object::get_outline_color());
1398   // shorten line length for spline to avoid arrow sticking
1399   position sp = strt;
1400   if (arrow_at_start) {
1401     position base = v[0] - strt;
1402     double hyp = hypot(base);
1403     if (hyp == 0.0) {
1404       error("cannot draw arrow on object with zero length");
1405       return;
1406     }
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
1412     } else {
1413       base *= fabs(lt.thickness) / hyp / 72 / 4;
1414       sp = strt + base;
1415       draw_arrow(sp, sp - v[0], aht, lt,
1416                  graphic_object::get_outline_color());
1417     }
1418   }
1419   if (arrow_at_end) {
1420     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1421     double hyp = hypot(base);
1422     if (hyp == 0.0) {
1423       error("cannot draw arrow on object with zero length");
1424       return;
1425     }
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
1431     } else {
1432       base *= fabs(lt.thickness) / hyp / 72 / 4;
1433       v[n-1] = en - base;
1434       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1435                  graphic_object::get_outline_color());
1436     }
1437   }
1438   out->spline(sp, v, n, lt);
1439   out->reset_color();
1440 }
1441
1442 line_object::~line_object()
1443 {
1444   a_delete v;
1445 }
1446
1447 linear_object *object_spec::make_line(position *curpos, direction *dirp)
1448 {
1449   static position last_line;
1450   static int have_last_line = 0;
1451   *dirp = dir;
1452   // We handle `at' only in conjunction with `with', otherwise it is
1453   // the same as the `from' attribute.
1454   position startpos;
1455   if ((flags & HAS_AT) && (flags & HAS_WITH))
1456     // handled later -- we need the end position
1457     startpos = *curpos;
1458   else if (flags & HAS_FROM)
1459     startpos = from;
1460   else
1461     startpos = *curpos;
1462   if (!(flags & HAS_SEGMENT)) {
1463     if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
1464         && have_last_line)
1465       segment_pos = last_line;
1466     else 
1467       switch (dir) {
1468       case UP_DIRECTION:
1469         segment_pos.y = segment_height;
1470         break;
1471       case DOWN_DIRECTION:
1472         segment_pos.y = -segment_height;
1473         break;
1474       case LEFT_DIRECTION:
1475         segment_pos.x = -segment_width;
1476         break;
1477       case RIGHT_DIRECTION:
1478         segment_pos.x = segment_width;
1479         break;
1480       default:
1481         assert(0);
1482       }
1483   }
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;
1487   segment_list = 0;
1488   while (old != 0) {
1489     segment *tem = old->next;
1490     old->next = segment_list;
1491     segment_list = old;
1492     old = tem;
1493   }
1494   // Absolutise all movements
1495   position endpos = startpos;
1496   int nsegments = 0;
1497   segment *s;
1498   for (s = segment_list; s; s = s->next, nsegments++)
1499     if (s->is_absolute)
1500       endpos = s->pos;
1501     else {
1502       endpos += s->pos;
1503       s->pos = endpos;
1504       s->is_absolute = 1;       // to avoid confusion
1505     }
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);
1509     position pos = at;
1510     place offset;
1511     place here;
1512     here.obj = &tmpobj;
1513     if (!with->follow(here, &offset))
1514       return 0;
1515     pos -= offset;
1516     for (s = segment_list; s; s = s->next)
1517       s->pos += pos;
1518     startpos += pos;
1519     endpos += pos;
1520   }
1521   // handle chop
1522   line_object *p = 0;
1523   position *v = new position[nsegments];
1524   int i = 0;
1525   for (s = segment_list; s; s = s->next, i++)
1526     v[i] = s->pos;
1527   if (flags & IS_DEFAULT_CHOPPED) {
1528     lookup_variable("circlerad", &start_chop);
1529     end_chop = start_chop;
1530     flags |= IS_CHOPPED;
1531   }
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);
1537     }
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);
1542     }
1543     startpos += start_chop_vec;
1544     v[nsegments - 1] -= end_chop_vec;
1545     endpos -= end_chop_vec;
1546   }
1547   switch (type) {
1548   case SPLINE_OBJECT:
1549     p = new spline_object(startpos, endpos, v, nsegments);
1550     break;
1551   case ARROW_OBJECT:
1552     p = new arrow_object(startpos, endpos, v, nsegments);
1553     break;
1554   case LINE_OBJECT:
1555     p = new line_object(startpos, endpos, v, nsegments);
1556     break;
1557   default:
1558     assert(0);
1559   }
1560   have_last_line = 1;
1561   last_line = endpos - startpos;
1562   *curpos = endpos;
1563   return p;
1564 }
1565
1566 class arc_object : public linear_object {
1567   int clockwise;
1568   position cent;
1569   double rad;
1570 public:
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; }
1575   position north();
1576   position south();
1577   position east();
1578   position west();
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; }
1585   void print();
1586   void move_by(const position &pos);
1587 };
1588
1589 arc_object::arc_object(int cw, const position &s, const position &e,
1590                        const position &c)
1591 : linear_object(s, e), clockwise(cw), cent(c)
1592 {
1593   rad = hypot(c - s);
1594 }
1595
1596 void arc_object::move_by(const position &pos)
1597 {
1598   linear_object::move_by(pos);
1599   cent += pos;
1600 }
1601
1602 // we get arc corners from the corresponding circle
1603
1604 position arc_object::north()
1605 {
1606   position result(cent);
1607   result.y += rad;
1608   return result;
1609 }
1610
1611 position arc_object::south()
1612 {
1613   position result(cent);
1614   result.y -= rad;
1615   return result;
1616 }
1617
1618 position arc_object::east()
1619 {
1620   position result(cent);
1621   result.x += rad;
1622   return result;
1623 }
1624
1625 position arc_object::west()
1626 {
1627   position result(cent);
1628   result.x -= rad;
1629   return result;
1630 }
1631
1632 position arc_object::north_east()
1633 {
1634   position result(cent);
1635   result.x += rad/M_SQRT2;
1636   result.y += rad/M_SQRT2;
1637   return result;
1638 }
1639
1640 position arc_object::north_west()
1641 {
1642   position result(cent);
1643   result.x -= rad/M_SQRT2;
1644   result.y += rad/M_SQRT2;
1645   return result;
1646 }
1647
1648 position arc_object::south_east()
1649 {
1650   position result(cent);
1651   result.x += rad/M_SQRT2;
1652   result.y -= rad/M_SQRT2;
1653   return result;
1654 }
1655
1656 position arc_object::south_west()
1657 {
1658   position result(cent);
1659   result.x -= rad/M_SQRT2;
1660   result.y -= rad/M_SQRT2;
1661   return result;
1662 }
1663
1664
1665 void arc_object::print()
1666 {
1667   if (lt.type == line_type::invisible)
1668     return;
1669   out->set_color(0, graphic_object::get_outline_color());
1670   // handle arrow direction; make shorter line for arc
1671   position sp, ep, b;
1672   if (clockwise) {
1673     sp = en;
1674     ep = strt;
1675   } else {
1676     sp = strt;
1677     ep = en;
1678   }
1679   if (arrow_at_start) {
1680     double theta = aht.height / rad;
1681     if (clockwise)
1682       theta = - theta;
1683     b = strt - cent;
1684     b = position(b.x*cos(theta) - b.y*sin(theta),
1685                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1686     if (clockwise)
1687       ep = b;
1688     else
1689       sp = b;
1690     if (aht.solid && out->supports_filled_polygons()) {
1691       draw_arrow(strt, strt - b, aht, lt,
1692                  graphic_object::get_outline_color());
1693     } else {
1694       position v = b;
1695       theta = fabs(lt.thickness) / 72 / 4 / rad;
1696       if (clockwise)
1697         theta = - theta;
1698       b = strt - cent;
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);
1704     }
1705   }
1706   if (arrow_at_end) {
1707     double theta = aht.height / rad;
1708     if (!clockwise)
1709       theta = - theta;
1710     b = en - cent;
1711     b = position(b.x*cos(theta) - b.y*sin(theta),
1712                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1713     if (clockwise)
1714       sp = b;
1715     else
1716       ep = b;
1717     if (aht.solid && out->supports_filled_polygons()) {
1718       draw_arrow(en, en - b, aht, lt,
1719                  graphic_object::get_outline_color());
1720     } else {
1721       position v = b;
1722       theta = fabs(lt.thickness) / 72 / 4 / rad;
1723       if (!clockwise)
1724         theta = - theta;
1725       b = en - cent;
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);
1731     }
1732   }
1733   out->arc(sp, cent, ep, lt);
1734   out->reset_color();
1735 }
1736
1737 inline double max(double a, double b)
1738 {
1739   return a > b ? a : b;
1740 }
1741
1742 void arc_object::update_bounding_box(bounding_box *p)
1743 {
1744   p->encompass(strt);
1745   p->encompass(en);
1746   position start_offset = strt - cent;
1747   if (start_offset.x == 0.0 && start_offset.y == 0.0)
1748     return;
1749   position end_offset = en  - cent;
1750   if (end_offset.x == 0.0 && end_offset.y == 0.0)
1751     return;
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);
1754   if (clockwise) {
1755     double temp = start_quad;
1756     start_quad = end_quad;
1757     end_quad = temp;
1758   }
1759   if (start_quad < 0.0)
1760     start_quad += 4.0;
1761   while (end_quad <= start_quad)
1762     end_quad += 4.0;
1763   double r = max(hypot(start_offset), hypot(end_offset));
1764   for (int q = int(start_quad) + 1; q < end_quad; q++) {
1765     position offset;
1766     switch (q % 4) {
1767     case 0:
1768       offset.x = r;
1769       break;
1770     case 1:
1771       offset.y = r;
1772       break;
1773     case 2:
1774       offset.x = -r;
1775       break;
1776     case 3:
1777       offset.y = -r;
1778       break;
1779     }
1780     p->encompass(cent + offset);
1781   }
1782 }
1783
1784 // We ignore the with attribute. The at attribute always refers to the center.
1785
1786 linear_object *object_spec::make_arc(position *curpos, direction *dirp)
1787 {
1788   *dirp = dir;
1789   int cw = (flags & IS_CLOCKWISE) != 0;
1790   // compute the start
1791   position startpos;
1792   if (flags & HAS_FROM)
1793     startpos = from;
1794   else
1795     startpos = *curpos;
1796   if (!(flags & HAS_RADIUS))
1797     lookup_variable("arcrad", &radius);
1798   // compute the end
1799   position endpos;
1800   if (flags & HAS_TO)
1801     endpos = to;
1802   else {
1803     position m(radius, radius);
1804     // Adjust the signs.
1805     if (cw) {
1806       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1807         m.x = -m.x;
1808       if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
1809         m.y = -m.y;
1810       *dirp = direction((dir + 3) % 4);
1811     }
1812     else {
1813       if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
1814         m.x = -m.x;
1815       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1816         m.y = -m.y;
1817       *dirp = direction((dir + 1) % 4);
1818     }
1819     endpos = startpos + m;
1820   }
1821   // compute the center
1822   position centerpos;
1823   if (flags & HAS_AT)
1824     centerpos = at;
1825   else if (startpos == endpos)
1826     centerpos = startpos;
1827   else {
1828     position h = (endpos - startpos)/2.0;
1829     double d = hypot(h);
1830     if (radius <= 0)
1831       radius = .25;
1832     // make the radius big enough
1833     while (radius < d)
1834       radius *= 2.0;
1835     double alpha = acos(d/radius);
1836     double theta = atan2(h.y, h.x);
1837     if (cw)
1838       theta -= alpha;
1839     else
1840       theta += alpha;
1841     centerpos = position(cos(theta), sin(theta))*radius + startpos;
1842   }
1843   arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
1844   *curpos = endpos;
1845   return p;
1846 }
1847
1848 graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
1849 {
1850   linear_object *obj;
1851   if (type == ARC_OBJECT)
1852     obj = make_arc(curpos, dirp);
1853   else
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))) {
1859     arrow_head_type a;
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)
1863       a.height = height;
1864     else
1865       lookup_variable("arrowht", &a.height);
1866     if (flags & HAS_WIDTH)
1867       a.width = width;
1868     else
1869       lookup_variable("arrowwid", &a.width);
1870     double solid;
1871     lookup_variable("arrowhead", &solid);
1872     a.solid = solid != 0.0;
1873     obj->add_arrows(at_start, at_end, a);
1874   }
1875   return obj;
1876 }
1877
1878 object *object_spec::make_object(position *curpos, direction *dirp)
1879 {
1880   graphic_object *obj = 0;
1881   switch (type) {
1882   case BLOCK_OBJECT:
1883     obj = make_block(curpos, dirp);
1884     break;
1885   case BOX_OBJECT:
1886     obj = make_box(curpos, dirp);
1887     break;
1888   case TEXT_OBJECT:
1889     obj = make_text(curpos, dirp);
1890     break;
1891   case ELLIPSE_OBJECT:
1892     obj = make_ellipse(curpos, dirp);
1893     break;
1894   case CIRCLE_OBJECT:
1895     obj = make_circle(curpos, dirp);
1896     break;
1897   case MOVE_OBJECT:
1898     obj = make_move(curpos, dirp);
1899     break;
1900   case ARC_OBJECT:
1901   case LINE_OBJECT:
1902   case SPLINE_OBJECT:
1903   case ARROW_OBJECT:
1904     obj = make_linear(curpos, dirp);
1905     break;
1906   case MARK_OBJECT:
1907   case OTHER_OBJECT:
1908   default:
1909     assert(0);
1910     break;
1911   }
1912   if (obj) {
1913     if (flags & IS_INVISIBLE)
1914       obj->set_invisible();
1915     if (text != 0)
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);
1921     double th;
1922     if (flags & HAS_THICKNESS)
1923       th = thickness;
1924     else
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);
1936       else {
1937         if (flags & IS_DEFAULT_FILLED)
1938           lookup_variable("fillval", &fill);
1939         if (fill < 0.0)
1940           error("bad fill value %1", fill);
1941         else
1942           obj->set_fill(fill);
1943       }
1944     }
1945   }
1946   return obj;
1947 }
1948
1949 struct string_list {
1950   string_list *next;
1951   char *str;
1952   string_list(char *);
1953   ~string_list();
1954 };
1955
1956 string_list::string_list(char *s)
1957 : next(0), str(s)
1958 {
1959 }
1960
1961 string_list::~string_list()
1962 {
1963   a_delete str;
1964 }
1965   
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.
1970
1971    For compatibility with DWB pic, `with' accepts positions also (this
1972    is incorrectly documented in CSTR 116). */
1973
1974 path::path(corner c)
1975 : crn(c), label_list(0), ypath(0), is_position(0)
1976 {
1977 }
1978
1979 path::path(position p)
1980 : crn(0), label_list(0), ypath(0), is_position(1)
1981 {
1982   pos.x = p.x;
1983   pos.y = p.y;
1984 }
1985
1986 path::path(char *l, corner c)
1987 : crn(c), ypath(0), is_position(0)
1988 {
1989   label_list = new string_list(l);
1990 }
1991
1992 path::~path()
1993 {
1994   while (label_list) {
1995     string_list *tem = label_list;
1996     label_list = label_list->next;
1997     delete tem;
1998   }
1999   delete ypath;
2000 }
2001
2002 void path::append(corner c)
2003 {
2004   assert(crn == 0);
2005   crn = c;
2006 }
2007
2008 void path::append(char *s)
2009 {
2010   string_list **p;
2011   for (p = &label_list; *p; p = &(*p)->next)
2012     ;
2013   *p = new string_list(s);
2014 }
2015
2016 void path::set_ypath(path *p)
2017 {
2018   ypath = p;
2019 }
2020
2021 // return non-zero for success
2022
2023 int path::follow(const place &pl, place *result) const
2024 {
2025   if (is_position) {
2026     result->x = pos.x;
2027     result->y = pos.y;
2028     result->obj = 0;
2029     return 1;
2030   }
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);
2035       return 0;
2036     }
2037   if (crn == 0 || p->obj == 0)
2038     *result = *p;
2039   else {
2040     position ps = ((p->obj)->*(crn))();
2041     result->x = ps.x;
2042     result->y = ps.y;
2043     result->obj = 0;
2044   }
2045   if (ypath) {
2046     place tem;
2047     if (!ypath->follow(pl, &tem))
2048       return 0;
2049     result->y = tem.y;
2050     if (result->obj != tem.obj)
2051       result->obj = 0;
2052   }
2053   return 1;
2054 }
2055
2056 void print_object_list(object *p)
2057 {
2058   for (; p; p = p->next) {
2059     p->print();
2060     p->print_text();
2061   }
2062 }
2063
2064 void print_picture(object *obj)
2065 {
2066   bounding_box bb;
2067   for (object *p = obj; p; p = p->next)
2068     p->update_bounding_box(&bb);
2069   double scale;
2070   lookup_variable("scale", &scale);
2071   out->start_picture(scale, bb.ll, bb.ur);
2072   print_object_list(obj);
2073   out->finish_picture();
2074 }
2075