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