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