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