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