282546cf37b5239e9593eeaaef7318b5b0087774
[platform/upstream/groff.git] / src / preproc / pic / object.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2014  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 3 of the License, or
10 (at your option) any later 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
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20 #include "pic.h"
21 #include "ptable.h"
22 #include "object.h"
23
24 void print_object_list(object *);
25
26 line_type::line_type()
27 : type(solid), thickness(1.0)
28 {
29 }
30
31 output::output() : args(0), desired_height(0.0), desired_width(0.0)
32 {
33 }
34
35 output::~output()
36 {
37   a_delete args;
38 }
39
40 void output::set_desired_width_height(double wid, double ht)
41 {
42   desired_width = wid;
43   desired_height = ht;
44 }
45
46 void output::set_args(const char *s)
47 {
48   a_delete args;
49   if (s == 0 || *s == '\0')
50     args = 0;
51   else
52     args = strsave(s);
53 }
54
55 int output::supports_filled_polygons()
56 {
57   return 0;
58 }
59
60 void output::begin_block(const position &, const position &)
61 {
62 }
63
64 void output::end_block()
65 {
66 }
67
68 double output::compute_scale(double sc, const position &ll, const position &ur)
69 {
70   distance dim = ur - ll;
71   if (desired_width != 0.0 || desired_height != 0.0) {
72     sc = 0.0;
73     if (desired_width != 0.0) {
74       if (dim.x == 0.0)
75         error("width specified for picture with zero width");
76       else
77         sc = dim.x/desired_width;
78     }
79     if (desired_height != 0.0) {
80       if (dim.y == 0.0)
81         error("height specified for picture with zero height");
82       else {
83         double tem = dim.y/desired_height;
84         if (tem > sc)
85           sc = tem;
86       }
87     }
88     return sc == 0.0 ? 1.0 : sc;
89   }
90   else {
91     if (sc <= 0.0)
92       sc = 1.0;
93     distance sdim = dim/sc;
94     double max_width = 0.0;
95     lookup_variable("maxpswid", &max_width);
96     double max_height = 0.0;
97     lookup_variable("maxpsht", &max_height);
98     if ((max_width > 0.0 && sdim.x > max_width)
99         || (max_height > 0.0 && sdim.y > max_height)) {
100       double xscale = dim.x/max_width;
101       double yscale = dim.y/max_height;
102       return xscale > yscale ? xscale : yscale;
103     }
104     else
105       return sc;
106   }
107 }
108
109 position::position(const place &pl)
110 {
111   if (pl.obj != 0) {
112     // Use two statements to work around bug in SGI C++.
113     object *tem = pl.obj;
114     *this = tem->origin();
115   }
116   else {
117     x = pl.x;
118     y = pl.y;
119   }
120 }
121
122 position::position() : x(0.0), y(0.0)
123 {
124 }
125
126 position::position(double a, double b) : x(a), y(b)
127 {
128 }
129
130
131 int operator==(const position &a, const position &b)
132 {
133   return a.x == b.x && a.y == b.y;
134 }
135
136 int operator!=(const position &a, const position &b)
137 {
138   return a.x != b.x || a.y != b.y;
139 }
140
141 position &position::operator+=(const position &a)
142 {
143   x += a.x;
144   y += a.y;
145   return *this;
146 }
147
148 position &position::operator-=(const position &a)
149 {
150   x -= a.x;
151   y -= a.y;
152   return *this;
153 }
154
155 position &position::operator*=(double a)
156 {
157   x *= a;
158   y *= a;
159   return *this;
160 }
161
162 position &position::operator/=(double a)
163 {
164   x /= a;
165   y /= a;
166   return *this;
167 }
168
169 position operator-(const position &a)
170 {
171   return position(-a.x, -a.y);
172 }
173
174 position operator+(const position &a, const position &b)
175 {
176   return position(a.x + b.x, a.y + b.y);
177 }
178
179 position operator-(const position &a, const position &b)
180 {
181   return position(a.x - b.x, a.y - b.y);
182 }
183
184 position operator/(const position &a, double n)
185 {
186   return position(a.x/n, a.y/n);
187 }
188
189 position operator*(const position &a, double n)
190 {
191   return position(a.x*n, a.y*n);
192 }
193
194 // dot product
195
196 double operator*(const position &a, const position &b)
197 {
198   return a.x*b.x + a.y*b.y;
199 }
200
201 double hypot(const position &a)
202 {
203   return groff_hypot(a.x, a.y);
204 }
205
206 struct arrow_head_type {
207   double height;
208   double width;
209   int solid;
210 };
211
212 void draw_arrow(const position &pos, const distance &dir,
213                 const arrow_head_type &aht, const line_type &lt,
214                 char *outline_color_for_fill)
215 {
216   double hyp = hypot(dir);
217   if (hyp == 0.0) {
218     error("cannot draw arrow on object with zero length");
219     return;
220   }
221   position base = -dir;
222   base *= aht.height/hyp;
223   position n(dir.y, -dir.x);
224   n *= aht.width/(hyp*2.0);
225   line_type slt = lt;
226   slt.type = line_type::solid;
227   if (aht.solid && out->supports_filled_polygons()) {
228     position v[3];
229     v[0] = pos;
230     v[1] = pos + base + n;
231     v[2] = pos + base - n;
232     // fill with outline color
233     out->set_color(outline_color_for_fill, outline_color_for_fill);
234     // make stroke thin to avoid arrow sticking
235     slt.thickness = 0.1;
236     out->polygon(v, 3, slt, 1);
237   }
238   else {
239     // use two line segments to avoid arrow sticking
240     out->line(pos + base - n, &pos, 1, slt);
241     out->line(pos + base + n, &pos, 1, slt);
242   }
243 }
244
245 object::object() : prev(0), next(0)
246 {
247 }
248
249 object::~object()
250 {
251 }
252
253 void object::move_by(const position &)
254 {
255 }
256
257 void object::print()
258 {
259 }
260
261 void object::print_text()
262 {
263 }
264
265 int object::blank()
266 {
267   return 0;
268 }
269
270 struct bounding_box {
271   int blank;
272   position ll;
273   position ur;
274
275   bounding_box();
276   void encompass(const position &);
277 };
278
279 bounding_box::bounding_box()
280 : blank(1)
281 {
282 }
283
284 void bounding_box::encompass(const position &pos)
285 {
286   if (blank) {
287     ll = pos;
288     ur = pos;
289     blank = 0;
290   }
291   else {
292     if (pos.x < ll.x)
293       ll.x = pos.x;
294     if (pos.y < ll.y)
295       ll.y = pos.y;
296     if (pos.x > ur.x)
297       ur.x = pos.x;
298     if (pos.y > ur.y)
299       ur.y = pos.y;
300   }
301 }
302
303 void object::update_bounding_box(bounding_box *)
304 {
305 }
306
307 position object::origin()
308 {
309   return position(0.0,0.0);
310 }
311
312 position object::north()
313 {
314   return origin();
315 }
316
317 position object::south()
318 {
319   return origin();
320 }
321
322 position object::east()
323 {
324   return origin();
325 }
326
327 position object::west()
328 {
329   return origin();
330 }
331
332 position object::north_east()
333 {
334   return origin();
335 }
336
337 position object::north_west()
338 {
339   return origin();
340 }
341
342 position object::south_east()
343 {
344   return origin();
345 }
346
347 position object::south_west()
348 {
349   return origin();
350 }
351
352 position object::start()
353 {
354   return origin();
355 }
356
357 position object::end()
358 {
359   return origin();
360 }
361
362 position object::center()
363 {
364   return origin();
365 }
366
367 double object::width()
368 {
369   return 0.0;
370 }
371
372 double object::radius()
373 {
374   return 0.0;
375 }
376
377 double object::height()
378 {
379   return 0.0;
380 }
381
382 place *object::find_label(const char *)
383 {
384   return 0;
385 }
386
387 segment::segment(const position &a, int n, segment *p)
388 : is_absolute(n), pos(a), next(p)
389 {
390 }
391
392 text_item::text_item(char *t, const char *fn, int ln)
393 : next(0), text(t), filename(fn), lineno(ln) 
394 {
395   adj.h = CENTER_ADJUST;
396   adj.v = NONE_ADJUST;
397 }
398
399 text_item::~text_item()
400 {
401   a_delete text;
402 }
403
404 object_spec::object_spec(object_type t) : type(t)
405 {
406   flags = 0;
407   tbl = 0;
408   segment_list = 0;
409   segment_width = segment_height = 0.0;
410   segment_is_absolute = 0;
411   text = 0;
412   shaded = 0;
413   xslanted = 0;
414   yslanted = 0;
415   outlined = 0;
416   with = 0;
417   dir = RIGHT_DIRECTION;
418 }
419
420 object_spec::~object_spec()
421 {
422   delete tbl;
423   while (segment_list != 0) {
424     segment *tem = segment_list;
425     segment_list = segment_list->next;
426     delete tem;
427   }
428   object *p = oblist.head;
429   while (p != 0) {
430     object *tem = p;
431     p = p->next;
432     delete tem;
433   }
434   while (text != 0) {
435     text_item *tem = text;
436     text = text->next;
437     delete tem;
438   }
439   delete with;
440   a_delete shaded;
441   a_delete outlined;
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   char *outline_color;
553   char *color_fill;
554 public:
555   graphic_object();
556   ~graphic_object();
557   object_type type() = 0;
558   void print_text();
559   void add_text(text_item *, int);
560   void set_dotted(double);
561   void set_dashed(double);
562   void set_thickness(double);
563   void set_invisible();
564   void set_outline_color(char *);
565   char *get_outline_color();
566   virtual void set_fill(double);
567   virtual void set_xslanted(double);
568   virtual void set_yslanted(double);
569   virtual void set_fill_color(char *);
570 };
571
572 graphic_object::graphic_object()
573 : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
574 {
575 }
576
577 void graphic_object::set_dotted(double wid)
578 {
579   lt.type = line_type::dotted;
580   lt.dash_width = wid;
581 }
582
583 void graphic_object::set_dashed(double wid)
584 {
585   lt.type = line_type::dashed;
586   lt.dash_width = wid;
587 }
588
589 void graphic_object::set_thickness(double th)
590 {
591   lt.thickness = th;
592 }
593
594 void graphic_object::set_fill(double)
595 {
596 }
597
598 void graphic_object::set_xslanted(double)
599 {
600 }
601
602 void graphic_object::set_yslanted(double)
603 {
604 }
605
606 void graphic_object::set_fill_color(char *c)
607 {
608   color_fill = strsave(c);
609 }
610
611 void graphic_object::set_outline_color(char *c)
612 {
613   outline_color = strsave(c);
614 }
615
616 char *graphic_object::get_outline_color()
617 {
618   return outline_color;
619 }
620
621 void graphic_object::set_invisible()
622 {
623   lt.type = line_type::invisible;
624 }
625
626 void graphic_object::add_text(text_item *t, int a)
627 {
628   aligned = a;
629   int len = 0;
630   text_item *p;
631   for (p = t; p; p = p->next)
632     len++;
633   if (len == 0)
634     text = 0;
635   else {
636     text = new text_piece[len];
637     for (p = t, len = 0; p; p = p->next, len++) {
638       text[len].text = p->text;
639       p->text = 0;
640       text[len].adj = p->adj;
641       text[len].filename = p->filename;
642       text[len].lineno = p->lineno;
643     }
644   }
645   ntext = len;
646 }
647
648 void graphic_object::print_text()
649 {
650   double angle = 0.0;
651   if (aligned) {
652     position d(end() - start());
653     if (d.x != 0.0 || d.y != 0.0)
654       angle = atan2(d.y, d.x);
655   }
656   if (text != 0) {
657     out->set_color(color_fill, get_outline_color());
658     out->text(center(), text, ntext, angle);
659     out->reset_color();
660   }
661 }
662
663 graphic_object::~graphic_object()
664 {
665   if (text)
666     ad_delete(ntext) text;
667 }
668
669 class rectangle_object : public graphic_object {
670 protected:
671   position cent;
672   position dim;
673 public:
674   rectangle_object(const position &);
675   double width() { return dim.x; }
676   double height() { return dim.y; }
677   position origin() { return cent; }
678   position center() { return cent; }
679   position north() { return position(cent.x, cent.y + dim.y/2.0); }
680   position south() { return position(cent.x, cent.y - dim.y/2.0); }
681   position east() { return position(cent.x + dim.x/2.0, cent.y); }
682   position west() { return position(cent.x - dim.x/2.0, cent.y); }
683   position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); }
684   position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); }
685   position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); }
686   position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); }
687   object_type type() = 0;
688   void update_bounding_box(bounding_box *);
689   void move_by(const position &);
690 };
691
692 rectangle_object::rectangle_object(const position &d)
693 : dim(d)
694 {
695 }
696
697 void rectangle_object::update_bounding_box(bounding_box *p)
698 {
699   p->encompass(cent - dim/2.0);
700   p->encompass(cent + dim/2.0);
701 }
702
703 void rectangle_object::move_by(const position &a)
704 {
705   cent += a;
706 }
707
708 class closed_object : public rectangle_object {
709 public:
710   closed_object(const position &);
711   object_type type() = 0;
712   void set_fill(double);
713   void set_xslanted(double);
714   void set_yslanted(double);
715   void set_fill_color(char *fill);
716 protected:
717   double fill;                  // < 0 if not filled
718   double xslanted;              // !=0 if x is slanted
719   double yslanted;              // !=0 if y is slanted
720   char *color_fill;             // = 0 if not colored
721 };
722
723 closed_object::closed_object(const position &pos)
724 : rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
725 {
726 }
727
728 void closed_object::set_fill(double f)
729 {
730   assert(f >= 0.0);
731   fill = f;
732 }
733
734 /* accept positive and negative values */ 
735 void closed_object::set_xslanted(double s)
736 {
737   //assert(s >= 0.0);
738   xslanted = s;
739 }
740 /* accept positive and negative values */ 
741 void closed_object::set_yslanted(double s)
742 {
743   //assert(s >= 0.0);
744   yslanted = s;
745 }
746
747 void closed_object::set_fill_color(char *f)
748 {
749   color_fill = strsave(f);
750 }
751
752 class box_object : public closed_object {
753   double xrad;
754   double yrad;
755 public:
756   box_object(const position &, double);
757   object_type type() { return BOX_OBJECT; }
758   void print();
759   position north_east();
760   position north_west();
761   position south_east();
762   position south_west();
763 };
764
765 box_object::box_object(const position &pos, double r)
766 : closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r)
767 {
768 }
769
770 const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
771
772 position box_object::north_east()
773 {
774   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
775                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
776 }
777
778 position box_object::north_west()
779 {
780   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
781                   cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
782 }
783
784 position box_object::south_east()
785 {
786   return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
787                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
788 }
789
790 position box_object::south_west()
791 {
792   return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
793                   cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
794 }
795
796 void box_object::print()
797 {
798   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
799     return;
800   out->set_color(color_fill, graphic_object::get_outline_color());
801   if (xrad == 0.0) {
802     distance dim2 = dim/2.0;
803     position vec[4];
804     /* error("x slanted %1", xslanted); */
805     /* error("y slanted %1", yslanted); */
806     vec[0] = cent + position(dim2.x, -(dim2.y - yslanted));          /* lr */
807     vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted);  /* ur */
808     vec[2] = cent + position(-(dim2.x - xslanted), dim2.y);          /* ul */
809     vec[3] = cent + position(-(dim2.x), -dim2.y);                    /* ll */
810     out->polygon(vec, 4, lt, fill);
811   }
812   else {
813     distance abs_dim(fabs(dim.x), fabs(dim.y));
814     out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill);
815   }
816   out->reset_color();
817 }
818
819 graphic_object *object_spec::make_box(position *curpos, direction *dirp)
820 {
821   static double last_box_height;
822   static double last_box_width;
823   static double last_box_radius;
824   static int have_last_box = 0;
825   if (!(flags & HAS_HEIGHT)) {
826     if ((flags & IS_SAME) && have_last_box)
827       height = last_box_height;
828     else
829       lookup_variable("boxht", &height);
830   }
831   if (!(flags & HAS_WIDTH)) {
832     if ((flags & IS_SAME) && have_last_box)
833       width = last_box_width;
834     else
835       lookup_variable("boxwid", &width);
836   }
837   if (!(flags & HAS_RADIUS)) {
838     if ((flags & IS_SAME) && have_last_box)
839       radius = last_box_radius;
840     else
841       lookup_variable("boxrad", &radius);
842   }
843   last_box_width = width;
844   last_box_height = height;
845   last_box_radius = radius;
846   have_last_box = 1;
847   radius = fabs(radius);
848   if (radius*2.0 > fabs(width))
849     radius = fabs(width/2.0);
850   if (radius*2.0 > fabs(height))
851     radius = fabs(height/2.0);
852   box_object *p = new box_object(position(width, height), radius);
853   if (!position_rectangle(p, curpos, dirp)) {
854     delete p;
855     p = 0;
856   }
857   return p;
858 }
859
860 // return non-zero for success
861
862 int object_spec::position_rectangle(rectangle_object *p,
863                                     position *curpos, direction *dirp)
864 {
865   position pos;
866   dir = *dirp;                  // ignore any direction in attribute list
867   position motion;
868   switch (dir) {
869   case UP_DIRECTION:
870     motion.y = p->height()/2.0;
871     break;
872   case DOWN_DIRECTION:
873     motion.y = -p->height()/2.0;
874     break;
875   case LEFT_DIRECTION:
876     motion.x = -p->width()/2.0;
877     break;
878   case RIGHT_DIRECTION:
879     motion.x = p->width()/2.0;
880     break;
881   default:
882     assert(0);
883   }
884   if (flags & HAS_AT) {
885     pos = at;
886     if (flags & HAS_WITH) {
887       place offset;
888       place here;
889       here.obj = p;
890       if (!with->follow(here, &offset))
891         return 0;
892       pos -= offset;
893     }
894   }
895   else {
896     pos = *curpos;
897     pos += motion;
898   }
899   p->move_by(pos);
900   pos += motion;
901   *curpos = pos;
902   return 1;
903 }
904
905 class block_object : public rectangle_object {
906   object_list oblist;
907   PTABLE(place) *tbl;
908 public:
909   block_object(const position &, const object_list &ol, PTABLE(place) *t);
910   ~block_object();
911   place *find_label(const char *);
912   object_type type();
913   void move_by(const position &);
914   void print();
915 };
916
917 block_object::block_object(const position &d, const object_list &ol,
918                            PTABLE(place) *t)
919 : rectangle_object(d), oblist(ol), tbl(t)
920 {
921 }
922
923 block_object::~block_object()
924 {
925   delete tbl;
926   object *p = oblist.head;
927   while (p != 0) {
928     object *tem = p;
929     p = p->next;
930     delete tem;
931   }
932 }
933
934 void block_object::print()
935 {
936   out->begin_block(south_west(), north_east());
937   print_object_list(oblist.head);
938   out->end_block();
939 }
940
941 static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
942 {
943   // Adjust all the labels that aren't attached to objects.
944   PTABLE_ITERATOR(place) iter(tbl);
945   const char *key;
946   place *pl;
947   while (iter.next(&key, &pl))
948     if (key && csupper(key[0]) && pl->obj == 0) {
949       pl->x += a.x;
950       pl->y += a.y;
951     }
952 }
953
954 void block_object::move_by(const position &a)
955 {
956   cent += a;
957   for (object *p = oblist.head; p; p = p->next)
958     p->move_by(a);
959   adjust_objectless_places(tbl, a);
960 }
961
962
963 place *block_object::find_label(const char *name)
964 {
965   return tbl->lookup(name);
966 }
967
968 object_type block_object::type()
969 {
970   return BLOCK_OBJECT;
971 }
972
973 graphic_object *object_spec::make_block(position *curpos, direction *dirp)
974 {
975   bounding_box bb;
976   for (object *p = oblist.head; p; p = p->next)
977     p->update_bounding_box(&bb);
978   position dim;
979   if (!bb.blank) {
980     position m = -(bb.ll + bb.ur)/2.0;
981     for (object *p = oblist.head; p; p = p->next)
982       p->move_by(m);
983     adjust_objectless_places(tbl, m);
984     dim = bb.ur - bb.ll;
985   }
986   if (flags & HAS_WIDTH)
987     dim.x = width;
988   if (flags & HAS_HEIGHT)
989     dim.y = height;
990   block_object *block = new block_object(dim, oblist, tbl);
991   if (!position_rectangle(block, curpos, dirp)) {
992     delete block;
993     block = 0;
994   }
995   tbl = 0;
996   oblist.head = oblist.tail = 0;
997   return block;
998 }
999
1000 class text_object : public rectangle_object {
1001 public:
1002   text_object(const position &);
1003   object_type type() { return TEXT_OBJECT; }
1004 };
1005
1006 text_object::text_object(const position &d)
1007 : rectangle_object(d)
1008 {
1009 }
1010
1011 graphic_object *object_spec::make_text(position *curpos, direction *dirp)
1012 {
1013   if (!(flags & HAS_HEIGHT)) {
1014     lookup_variable("textht", &height);
1015     int nitems = 0;
1016     for (text_item *t = text; t; t = t->next)
1017       nitems++;
1018     height *= nitems;
1019   }
1020   if (!(flags & HAS_WIDTH))
1021     lookup_variable("textwid", &width);
1022   text_object *p = new text_object(position(width, height));
1023   if (!position_rectangle(p, curpos, dirp)) {
1024     delete p;
1025     p = 0;
1026   }
1027   return p;
1028 }
1029
1030
1031 class ellipse_object : public closed_object {
1032 public:
1033   ellipse_object(const position &);
1034   position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1035                                           cent.y + dim.y/(M_SQRT2*2.0)); }
1036   position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1037                                           cent.y + dim.y/(M_SQRT2*2.0)); }
1038   position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
1039                                           cent.y - dim.y/(M_SQRT2*2.0)); }
1040   position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
1041                                           cent.y - dim.y/(M_SQRT2*2.0)); }
1042   double radius() { return dim.x/2.0; }
1043   object_type type() { return ELLIPSE_OBJECT; }
1044   void print();
1045 };
1046
1047 ellipse_object::ellipse_object(const position &d)
1048 : closed_object(d)
1049 {
1050 }
1051
1052 void ellipse_object::print()
1053 {
1054   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1055     return;
1056   out->set_color(color_fill, graphic_object::get_outline_color());
1057   out->ellipse(cent, dim, lt, fill);
1058   out->reset_color();
1059 }
1060
1061 graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
1062 {
1063   static double last_ellipse_height;
1064   static double last_ellipse_width;
1065   static int have_last_ellipse = 0;
1066   if (!(flags & HAS_HEIGHT)) {
1067     if ((flags & IS_SAME) && have_last_ellipse)
1068       height = last_ellipse_height;
1069     else
1070       lookup_variable("ellipseht", &height);
1071   }
1072   if (!(flags & HAS_WIDTH)) {
1073     if ((flags & IS_SAME) && have_last_ellipse)
1074       width = last_ellipse_width;
1075     else
1076       lookup_variable("ellipsewid", &width);
1077   }
1078   last_ellipse_width = width;
1079   last_ellipse_height = height;
1080   have_last_ellipse = 1;
1081   ellipse_object *p = new ellipse_object(position(width, height));
1082   if (!position_rectangle(p, curpos, dirp)) {
1083     delete p;
1084     return 0;
1085   }
1086   return p;
1087 }
1088
1089 class circle_object : public ellipse_object {
1090 public:
1091   circle_object(double);
1092   object_type type() { return CIRCLE_OBJECT; }
1093   void print();
1094 };
1095
1096 circle_object::circle_object(double diam)
1097 : ellipse_object(position(diam, diam))
1098 {
1099 }
1100
1101 void circle_object::print()
1102 {
1103   if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
1104     return;
1105   out->set_color(color_fill, graphic_object::get_outline_color());
1106   out->circle(cent, dim.x/2.0, lt, fill);
1107   out->reset_color();
1108 }
1109
1110 graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
1111 {
1112   static double last_circle_radius;
1113   static int have_last_circle = 0;
1114   if (!(flags & HAS_RADIUS)) {
1115     if ((flags & IS_SAME) && have_last_circle)
1116       radius = last_circle_radius;
1117     else
1118       lookup_variable("circlerad", &radius);
1119   }
1120   last_circle_radius = radius;
1121   have_last_circle = 1;
1122   circle_object *p = new circle_object(radius*2.0);
1123   if (!position_rectangle(p, curpos, dirp)) {
1124     delete p;
1125     return 0;
1126   }
1127   return p;
1128 }
1129
1130 class move_object : public graphic_object {
1131   position strt;
1132   position en;
1133 public:
1134   move_object(const position &s, const position &e);
1135   position origin() { return en; }
1136   object_type type() { return MOVE_OBJECT; }
1137   void update_bounding_box(bounding_box *);
1138   void move_by(const position &);
1139 };
1140
1141 move_object::move_object(const position &s, const position &e)
1142 : strt(s), en(e)
1143 {
1144 }
1145
1146 void move_object::update_bounding_box(bounding_box *p)
1147 {
1148   p->encompass(strt);
1149   p->encompass(en);
1150 }
1151
1152 void move_object::move_by(const position &a)
1153 {
1154   strt += a;
1155   en += a;
1156 }
1157
1158 graphic_object *object_spec::make_move(position *curpos, direction *dirp)
1159 {
1160   static position last_move;
1161   static int have_last_move = 0;
1162   *dirp = dir;
1163   // No need to look at at since `at' attribute sets `from' attribute.
1164   position startpos = (flags & HAS_FROM) ? from : *curpos;
1165   if (!(flags & HAS_SEGMENT)) {
1166     if ((flags & IS_SAME) && have_last_move)
1167       segment_pos = last_move;
1168     else {
1169       switch (dir) {
1170       case UP_DIRECTION:
1171         segment_pos.y = segment_height;
1172         break;
1173       case DOWN_DIRECTION:
1174         segment_pos.y = -segment_height;
1175         break;
1176       case LEFT_DIRECTION:
1177         segment_pos.x = -segment_width;
1178         break;
1179       case RIGHT_DIRECTION:
1180         segment_pos.x = segment_width;
1181         break;
1182       default:
1183         assert(0);
1184       }
1185     }
1186   }
1187   segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1188   // Reverse the segment_list so that it's in forward order.
1189   segment *old = segment_list;
1190   segment_list = 0;
1191   while (old != 0) {
1192     segment *tem = old->next;
1193     old->next = segment_list;
1194     segment_list = old;
1195     old = tem;
1196   }
1197   // Compute the end position.
1198   position endpos = startpos;
1199   for (segment *s = segment_list; s; s = s->next)
1200     if (s->is_absolute)
1201       endpos = s->pos;
1202     else 
1203       endpos += s->pos;
1204   have_last_move = 1;
1205   last_move = endpos - startpos;
1206   move_object *p = new move_object(startpos, endpos);
1207   *curpos = endpos;
1208   return p;
1209 }
1210
1211 class linear_object : public graphic_object {
1212 protected:
1213   char arrow_at_start;
1214   char arrow_at_end;
1215   arrow_head_type aht;
1216   position strt;
1217   position en;
1218 public:
1219   linear_object(const position &s, const position &e);
1220   position start() { return strt; }
1221   position end() { return en; }
1222   void move_by(const position &);
1223   void update_bounding_box(bounding_box *) = 0;
1224   object_type type() = 0;
1225   void add_arrows(int at_start, int at_end, const arrow_head_type &);
1226 };
1227
1228 class line_object : public linear_object {
1229 protected:
1230   position *v;
1231   int n;
1232 public:
1233   line_object(const position &s, const position &e, position *, int);
1234   ~line_object();
1235   position origin() { return strt; }
1236   position center() { return (strt + en)/2.0; }
1237   position north() { return (en.y - strt.y) > 0 ? en : strt; }
1238   position south() { return (en.y - strt.y) < 0 ? en : strt; }
1239   position east() { return (en.x - strt.x) > 0 ? en : strt; }
1240   position west() { return (en.x - strt.x) < 0 ? en : strt; }
1241   object_type type() { return LINE_OBJECT; }
1242   void update_bounding_box(bounding_box *);
1243   void print();
1244   void move_by(const position &);
1245 };
1246
1247 class arrow_object : public line_object {
1248 public:
1249   arrow_object(const position &, const position &, position *, int);
1250   object_type type() { return ARROW_OBJECT; }
1251 };
1252
1253 class spline_object : public line_object {
1254 public:
1255   spline_object(const position &, const position &, position *, int);
1256   object_type type() { return SPLINE_OBJECT; }
1257   void print();
1258   void update_bounding_box(bounding_box *);
1259 };
1260
1261 linear_object::linear_object(const position &s, const position &e)
1262 : arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
1263 {
1264 }
1265
1266 void linear_object::move_by(const position &a)
1267 {
1268   strt += a;
1269   en += a;
1270 }
1271
1272 void linear_object::add_arrows(int at_start, int at_end,
1273                                const arrow_head_type &a)
1274 {
1275   arrow_at_start = at_start;
1276   arrow_at_end = at_end;
1277   aht = a;
1278 }
1279
1280 line_object::line_object(const position &s, const position &e,
1281                          position *p, int i)
1282 : linear_object(s, e), v(p), n(i)
1283 {
1284 }
1285
1286 void line_object::print()
1287 {
1288   if (lt.type == line_type::invisible)
1289     return;
1290   out->set_color(0, graphic_object::get_outline_color());
1291   // shorten line length to avoid arrow sticking.
1292   position sp = strt;
1293   if (arrow_at_start) {
1294     position base = v[0] - strt;
1295     double hyp = hypot(base);
1296     if (hyp == 0.0) {
1297       error("cannot draw arrow on object with zero length");
1298       return;
1299     }
1300     if (aht.solid && out->supports_filled_polygons()) {
1301       base *= aht.height / hyp;
1302       draw_arrow(strt, strt - v[0], aht, lt,
1303                  graphic_object::get_outline_color());
1304       sp = strt + base;
1305     } else {
1306       base *= fabs(lt.thickness) / hyp / 72 / 4;
1307       sp = strt + base;
1308       draw_arrow(sp, sp - v[0], aht, lt,
1309                  graphic_object::get_outline_color());
1310     }
1311   }
1312   if (arrow_at_end) {
1313     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1314     double hyp = hypot(base);
1315     if (hyp == 0.0) {
1316       error("cannot draw arrow on object with zero length");
1317       return;
1318     }
1319     if (aht.solid && out->supports_filled_polygons()) {
1320       base *= aht.height / hyp;
1321       draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1322                  graphic_object::get_outline_color());
1323       v[n-1] = en - base;
1324     } else {
1325       base *= fabs(lt.thickness) / hyp / 72 / 4;
1326       v[n-1] = en - base;
1327       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1328                  graphic_object::get_outline_color());
1329     }
1330   }
1331   out->line(sp, v, n, lt);
1332   out->reset_color();
1333 }
1334
1335 void line_object::update_bounding_box(bounding_box *p)
1336 {
1337   p->encompass(strt);
1338   for (int i = 0; i < n; i++)
1339     p->encompass(v[i]);
1340 }
1341
1342 void line_object::move_by(const position &pos)
1343 {
1344   linear_object::move_by(pos);
1345   for (int i = 0; i < n; i++)
1346     v[i] += pos;
1347 }
1348   
1349 void spline_object::update_bounding_box(bounding_box *p)
1350 {
1351   p->encompass(strt);
1352   p->encompass(en);
1353   /*
1354
1355   If
1356
1357   p1 = q1/2 + q2/2
1358   p2 = q1/6 + q2*5/6
1359   p3 = q2*5/6 + q3/6
1360   p4 = q2/2 + q3/2
1361   [ the points for the Bezier cubic ]
1362
1363   and
1364
1365   t = .5
1366
1367   then
1368
1369   (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4
1370   [ the equation for the Bezier cubic ]
1371
1372   = .125*q1 + .75*q2 + .125*q3
1373
1374   */
1375   for (int i = 1; i < n; i++)
1376     p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125);
1377 }
1378
1379 arrow_object::arrow_object(const position &s, const position &e,
1380                            position *p, int i)
1381 : line_object(s, e, p, i)
1382 {
1383 }
1384
1385 spline_object::spline_object(const position &s, const position &e,
1386                              position *p, int i)
1387 : line_object(s, e, p, i)
1388 {
1389 }
1390
1391 void spline_object::print()
1392 {
1393   if (lt.type == line_type::invisible)
1394     return;
1395   out->set_color(0, graphic_object::get_outline_color());
1396   // shorten line length for spline to avoid arrow sticking
1397   position sp = strt;
1398   if (arrow_at_start) {
1399     position base = v[0] - strt;
1400     double hyp = hypot(base);
1401     if (hyp == 0.0) {
1402       error("cannot draw arrow on object with zero length");
1403       return;
1404     }
1405     if (aht.solid && out->supports_filled_polygons()) {
1406       base *= aht.height / hyp;
1407       draw_arrow(strt, strt - v[0], aht, lt,
1408                  graphic_object::get_outline_color());
1409       sp = strt + base*0.1; // to reserve spline shape
1410     } else {
1411       base *= fabs(lt.thickness) / hyp / 72 / 4;
1412       sp = strt + base;
1413       draw_arrow(sp, sp - v[0], aht, lt,
1414                  graphic_object::get_outline_color());
1415     }
1416   }
1417   if (arrow_at_end) {
1418     position base = v[n-1] - (n > 1 ? v[n-2] : strt);
1419     double hyp = hypot(base);
1420     if (hyp == 0.0) {
1421       error("cannot draw arrow on object with zero length");
1422       return;
1423     }
1424     if (aht.solid && out->supports_filled_polygons()) {
1425       base *= aht.height / hyp;
1426       draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1427                  graphic_object::get_outline_color());
1428       v[n-1] = en - base*0.1; // to reserve spline shape
1429     } else {
1430       base *= fabs(lt.thickness) / hyp / 72 / 4;
1431       v[n-1] = en - base;
1432       draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
1433                  graphic_object::get_outline_color());
1434     }
1435   }
1436   out->spline(sp, v, n, lt);
1437   out->reset_color();
1438 }
1439
1440 line_object::~line_object()
1441 {
1442   a_delete v;
1443 }
1444
1445 linear_object *object_spec::make_line(position *curpos, direction *dirp)
1446 {
1447   static position last_line;
1448   static int have_last_line = 0;
1449   *dirp = dir;
1450   // We handle `at' only in conjunction with `with', otherwise it is
1451   // the same as the `from' attribute.
1452   position startpos;
1453   if ((flags & HAS_AT) && (flags & HAS_WITH))
1454     // handled later -- we need the end position
1455     startpos = *curpos;
1456   else if (flags & HAS_FROM)
1457     startpos = from;
1458   else
1459     startpos = *curpos;
1460   if (!(flags & HAS_SEGMENT)) {
1461     if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
1462         && have_last_line)
1463       segment_pos = last_line;
1464     else 
1465       switch (dir) {
1466       case UP_DIRECTION:
1467         segment_pos.y = segment_height;
1468         break;
1469       case DOWN_DIRECTION:
1470         segment_pos.y = -segment_height;
1471         break;
1472       case LEFT_DIRECTION:
1473         segment_pos.x = -segment_width;
1474         break;
1475       case RIGHT_DIRECTION:
1476         segment_pos.x = segment_width;
1477         break;
1478       default:
1479         assert(0);
1480       }
1481   }
1482   segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
1483   // reverse the segment_list so that it's in forward order
1484   segment *old = segment_list;
1485   segment_list = 0;
1486   while (old != 0) {
1487     segment *tem = old->next;
1488     old->next = segment_list;
1489     segment_list = old;
1490     old = tem;
1491   }
1492   // Absolutise all movements
1493   position endpos = startpos;
1494   int nsegments = 0;
1495   segment *s;
1496   for (s = segment_list; s; s = s->next, nsegments++)
1497     if (s->is_absolute)
1498       endpos = s->pos;
1499     else {
1500       endpos += s->pos;
1501       s->pos = endpos;
1502       s->is_absolute = 1;       // to avoid confusion
1503     }
1504   if ((flags & HAS_AT) && (flags & HAS_WITH)) {
1505     // `tmpobj' works for arrows and splines too -- we only need positions
1506     line_object tmpobj(startpos, endpos, 0, 0);
1507     position pos = at;
1508     place offset;
1509     place here;
1510     here.obj = &tmpobj;
1511     if (!with->follow(here, &offset))
1512       return 0;
1513     pos -= offset;
1514     for (s = segment_list; s; s = s->next)
1515       s->pos += pos;
1516     startpos += pos;
1517     endpos += pos;
1518   }
1519   // handle chop
1520   line_object *p = 0;
1521   position *v = new position[nsegments];
1522   int i = 0;
1523   for (s = segment_list; s; s = s->next, i++)
1524     v[i] = s->pos;
1525   if (flags & IS_DEFAULT_CHOPPED) {
1526     lookup_variable("circlerad", &start_chop);
1527     end_chop = start_chop;
1528     flags |= IS_CHOPPED;
1529   }
1530   if (flags & IS_CHOPPED) {
1531     position start_chop_vec, end_chop_vec;
1532     if (start_chop != 0.0) {
1533       start_chop_vec = v[0] - startpos;
1534       start_chop_vec *= start_chop / hypot(start_chop_vec);
1535     }
1536     if (end_chop != 0.0) {
1537       end_chop_vec = (v[nsegments - 1]
1538                       - (nsegments > 1 ? v[nsegments - 2] : startpos));
1539       end_chop_vec *= end_chop / hypot(end_chop_vec);
1540     }
1541     startpos += start_chop_vec;
1542     v[nsegments - 1] -= end_chop_vec;
1543     endpos -= end_chop_vec;
1544   }
1545   switch (type) {
1546   case SPLINE_OBJECT:
1547     p = new spline_object(startpos, endpos, v, nsegments);
1548     break;
1549   case ARROW_OBJECT:
1550     p = new arrow_object(startpos, endpos, v, nsegments);
1551     break;
1552   case LINE_OBJECT:
1553     p = new line_object(startpos, endpos, v, nsegments);
1554     break;
1555   default:
1556     assert(0);
1557   }
1558   have_last_line = 1;
1559   last_line = endpos - startpos;
1560   *curpos = endpos;
1561   return p;
1562 }
1563
1564 class arc_object : public linear_object {
1565   int clockwise;
1566   position cent;
1567   double rad;
1568 public:
1569   arc_object(int, const position &, const position &, const position &);
1570   position origin() { return cent; }
1571   position center() { return cent; }
1572   double radius() { return rad; }
1573   position north();
1574   position south();
1575   position east();
1576   position west();
1577   position north_east();
1578   position north_west();
1579   position south_east();
1580   position south_west();
1581   void update_bounding_box(bounding_box *);
1582   object_type type() { return ARC_OBJECT; }
1583   void print();
1584   void move_by(const position &pos);
1585 };
1586
1587 arc_object::arc_object(int cw, const position &s, const position &e,
1588                        const position &c)
1589 : linear_object(s, e), clockwise(cw), cent(c)
1590 {
1591   rad = hypot(c - s);
1592 }
1593
1594 void arc_object::move_by(const position &pos)
1595 {
1596   linear_object::move_by(pos);
1597   cent += pos;
1598 }
1599
1600 // we get arc corners from the corresponding circle
1601
1602 position arc_object::north()
1603 {
1604   position result(cent);
1605   result.y += rad;
1606   return result;
1607 }
1608
1609 position arc_object::south()
1610 {
1611   position result(cent);
1612   result.y -= rad;
1613   return result;
1614 }
1615
1616 position arc_object::east()
1617 {
1618   position result(cent);
1619   result.x += rad;
1620   return result;
1621 }
1622
1623 position arc_object::west()
1624 {
1625   position result(cent);
1626   result.x -= rad;
1627   return result;
1628 }
1629
1630 position arc_object::north_east()
1631 {
1632   position result(cent);
1633   result.x += rad/M_SQRT2;
1634   result.y += rad/M_SQRT2;
1635   return result;
1636 }
1637
1638 position arc_object::north_west()
1639 {
1640   position result(cent);
1641   result.x -= rad/M_SQRT2;
1642   result.y += rad/M_SQRT2;
1643   return result;
1644 }
1645
1646 position arc_object::south_east()
1647 {
1648   position result(cent);
1649   result.x += rad/M_SQRT2;
1650   result.y -= rad/M_SQRT2;
1651   return result;
1652 }
1653
1654 position arc_object::south_west()
1655 {
1656   position result(cent);
1657   result.x -= rad/M_SQRT2;
1658   result.y -= rad/M_SQRT2;
1659   return result;
1660 }
1661
1662
1663 void arc_object::print()
1664 {
1665   if (lt.type == line_type::invisible)
1666     return;
1667   out->set_color(0, graphic_object::get_outline_color());
1668   // handle arrow direction; make shorter line for arc
1669   position sp, ep, b;
1670   if (clockwise) {
1671     sp = en;
1672     ep = strt;
1673   } else {
1674     sp = strt;
1675     ep = en;
1676   }
1677   if (arrow_at_start) {
1678     double theta = aht.height / rad;
1679     if (clockwise)
1680       theta = - theta;
1681     b = strt - cent;
1682     b = position(b.x*cos(theta) - b.y*sin(theta),
1683                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1684     if (clockwise)
1685       ep = b;
1686     else
1687       sp = b;
1688     if (aht.solid && out->supports_filled_polygons()) {
1689       draw_arrow(strt, strt - b, aht, lt,
1690                  graphic_object::get_outline_color());
1691     } else {
1692       position v = b;
1693       theta = fabs(lt.thickness) / 72 / 4 / rad;
1694       if (clockwise)
1695         theta = - theta;
1696       b = strt - cent;
1697       b = position(b.x*cos(theta) - b.y*sin(theta),
1698                    b.x*sin(theta) + b.y*cos(theta)) + cent;
1699       draw_arrow(b, b - v, aht, lt,
1700                  graphic_object::get_outline_color());
1701       out->line(b, &v, 1, lt);
1702     }
1703   }
1704   if (arrow_at_end) {
1705     double theta = aht.height / rad;
1706     if (!clockwise)
1707       theta = - theta;
1708     b = en - cent;
1709     b = position(b.x*cos(theta) - b.y*sin(theta),
1710                  b.x*sin(theta) + b.y*cos(theta)) + cent;
1711     if (clockwise)
1712       sp = b;
1713     else
1714       ep = b;
1715     if (aht.solid && out->supports_filled_polygons()) {
1716       draw_arrow(en, en - b, aht, lt,
1717                  graphic_object::get_outline_color());
1718     } else {
1719       position v = b;
1720       theta = fabs(lt.thickness) / 72 / 4 / rad;
1721       if (!clockwise)
1722         theta = - theta;
1723       b = en - cent;
1724       b = position(b.x*cos(theta) - b.y*sin(theta),
1725                    b.x*sin(theta) + b.y*cos(theta)) + cent;
1726       draw_arrow(b, b - v, aht, lt,
1727                  graphic_object::get_outline_color());
1728       out->line(b, &v, 1, lt);
1729     }
1730   }
1731   out->arc(sp, cent, ep, lt);
1732   out->reset_color();
1733 }
1734
1735 inline double max(double a, double b)
1736 {
1737   return a > b ? a : b;
1738 }
1739
1740 void arc_object::update_bounding_box(bounding_box *p)
1741 {
1742   p->encompass(strt);
1743   p->encompass(en);
1744   position start_offset = strt - cent;
1745   if (start_offset.x == 0.0 && start_offset.y == 0.0)
1746     return;
1747   position end_offset = en  - cent;
1748   if (end_offset.x == 0.0 && end_offset.y == 0.0)
1749     return;
1750   double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0);
1751   double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0);
1752   if (clockwise) {
1753     double temp = start_quad;
1754     start_quad = end_quad;
1755     end_quad = temp;
1756   }
1757   if (start_quad < 0.0)
1758     start_quad += 4.0;
1759   while (end_quad <= start_quad)
1760     end_quad += 4.0;
1761   double r = max(hypot(start_offset), hypot(end_offset));
1762   for (int q = int(start_quad) + 1; q < end_quad; q++) {
1763     position offset;
1764     switch (q % 4) {
1765     case 0:
1766       offset.x = r;
1767       break;
1768     case 1:
1769       offset.y = r;
1770       break;
1771     case 2:
1772       offset.x = -r;
1773       break;
1774     case 3:
1775       offset.y = -r;
1776       break;
1777     }
1778     p->encompass(cent + offset);
1779   }
1780 }
1781
1782 // We ignore the with attribute. The at attribute always refers to the center.
1783
1784 linear_object *object_spec::make_arc(position *curpos, direction *dirp)
1785 {
1786   *dirp = dir;
1787   int cw = (flags & IS_CLOCKWISE) != 0;
1788   // compute the start
1789   position startpos;
1790   if (flags & HAS_FROM)
1791     startpos = from;
1792   else
1793     startpos = *curpos;
1794   if (!(flags & HAS_RADIUS))
1795     lookup_variable("arcrad", &radius);
1796   // compute the end
1797   position endpos;
1798   if (flags & HAS_TO)
1799     endpos = to;
1800   else {
1801     position m(radius, radius);
1802     // Adjust the signs.
1803     if (cw) {
1804       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1805         m.x = -m.x;
1806       if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
1807         m.y = -m.y;
1808       *dirp = direction((dir + 3) % 4);
1809     }
1810     else {
1811       if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
1812         m.x = -m.x;
1813       if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
1814         m.y = -m.y;
1815       *dirp = direction((dir + 1) % 4);
1816     }
1817     endpos = startpos + m;
1818   }
1819   // compute the center
1820   position centerpos;
1821   if (flags & HAS_AT)
1822     centerpos = at;
1823   else if (startpos == endpos)
1824     centerpos = startpos;
1825   else {
1826     position h = (endpos - startpos)/2.0;
1827     double d = hypot(h);
1828     if (radius <= 0)
1829       radius = .25;
1830     // make the radius big enough
1831     if (radius < d)
1832       radius = d;
1833     double alpha = acos(d/radius);
1834     double theta = atan2(h.y, h.x);
1835     if (cw)
1836       theta -= alpha;
1837     else
1838       theta += alpha;
1839     centerpos = position(cos(theta), sin(theta))*radius + startpos;
1840   }
1841   arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
1842   *curpos = endpos;
1843   return p;
1844 }
1845
1846 graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
1847 {
1848   linear_object *obj;
1849   if (type == ARC_OBJECT)
1850     obj = make_arc(curpos, dirp);
1851   else
1852     obj = make_line(curpos, dirp);
1853   if (type == ARROW_OBJECT
1854       && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0)
1855     flags |= HAS_RIGHT_ARROW_HEAD;
1856   if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) {
1857     arrow_head_type a;
1858     int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0;
1859     int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0;
1860     if (flags & HAS_HEIGHT)
1861       a.height = height;
1862     else
1863       lookup_variable("arrowht", &a.height);
1864     if (flags & HAS_WIDTH)
1865       a.width = width;
1866     else
1867       lookup_variable("arrowwid", &a.width);
1868     double solid;
1869     lookup_variable("arrowhead", &solid);
1870     a.solid = solid != 0.0;
1871     obj->add_arrows(at_start, at_end, a);
1872   }
1873   return obj;
1874 }
1875
1876 object *object_spec::make_object(position *curpos, direction *dirp)
1877 {
1878   graphic_object *obj = 0;
1879   switch (type) {
1880   case BLOCK_OBJECT:
1881     obj = make_block(curpos, dirp);
1882     break;
1883   case BOX_OBJECT:
1884     obj = make_box(curpos, dirp);
1885     break;
1886   case TEXT_OBJECT:
1887     obj = make_text(curpos, dirp);
1888     break;
1889   case ELLIPSE_OBJECT:
1890     obj = make_ellipse(curpos, dirp);
1891     break;
1892   case CIRCLE_OBJECT:
1893     obj = make_circle(curpos, dirp);
1894     break;
1895   case MOVE_OBJECT:
1896     obj = make_move(curpos, dirp);
1897     break;
1898   case ARC_OBJECT:
1899   case LINE_OBJECT:
1900   case SPLINE_OBJECT:
1901   case ARROW_OBJECT:
1902     obj = make_linear(curpos, dirp);
1903     break;
1904   case MARK_OBJECT:
1905   case OTHER_OBJECT:
1906   default:
1907     assert(0);
1908     break;
1909   }
1910   if (obj) {
1911     if (flags & IS_INVISIBLE)
1912       obj->set_invisible();
1913     if (text != 0)
1914       obj->add_text(text, (flags & IS_ALIGNED) != 0);
1915     if (flags & IS_DOTTED)
1916       obj->set_dotted(dash_width);
1917     else if (flags & IS_DASHED)
1918       obj->set_dashed(dash_width);
1919     double th;
1920     if (flags & HAS_THICKNESS)
1921       th = thickness;
1922     else
1923       lookup_variable("linethick", &th);
1924     obj->set_thickness(th);
1925     if (flags & IS_OUTLINED)
1926       obj->set_outline_color(outlined);
1927     if (flags & IS_XSLANTED)
1928       obj->set_xslanted(xslanted);
1929     if (flags & IS_YSLANTED)
1930       obj->set_yslanted(yslanted);
1931     if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) {
1932       if (flags & IS_SHADED)
1933         obj->set_fill_color(shaded);
1934       else {
1935         if (flags & IS_DEFAULT_FILLED)
1936           lookup_variable("fillval", &fill);
1937         if (fill < 0.0)
1938           error("bad fill value %1", fill);
1939         else
1940           obj->set_fill(fill);
1941       }
1942     }
1943   }
1944   return obj;
1945 }
1946
1947 struct string_list {
1948   string_list *next;
1949   char *str;
1950   string_list(char *);
1951   ~string_list();
1952 };
1953
1954 string_list::string_list(char *s)
1955 : next(0), str(s)
1956 {
1957 }
1958
1959 string_list::~string_list()
1960 {
1961   a_delete str;
1962 }
1963   
1964 /* A path is used to hold the argument to the `with' attribute.  For
1965    example, `.nw' or `.A.s' or `.A'.  The major operation on a path is to
1966    take a place and follow the path through the place to place within the
1967    place.  Note that `.A.B.C.sw' will work.
1968
1969    For compatibility with DWB pic, `with' accepts positions also (this
1970    is incorrectly documented in CSTR 116). */
1971
1972 path::path(corner c)
1973 : crn(c), label_list(0), ypath(0), is_position(0)
1974 {
1975 }
1976
1977 path::path(position p)
1978 : crn(0), label_list(0), ypath(0), is_position(1)
1979 {
1980   pos.x = p.x;
1981   pos.y = p.y;
1982 }
1983
1984 path::path(char *l, corner c)
1985 : crn(c), ypath(0), is_position(0)
1986 {
1987   label_list = new string_list(l);
1988 }
1989
1990 path::~path()
1991 {
1992   while (label_list) {
1993     string_list *tem = label_list;
1994     label_list = label_list->next;
1995     delete tem;
1996   }
1997   delete ypath;
1998 }
1999
2000 void path::append(corner c)
2001 {
2002   assert(crn == 0);
2003   crn = c;
2004 }
2005
2006 void path::append(char *s)
2007 {
2008   string_list **p;
2009   for (p = &label_list; *p; p = &(*p)->next)
2010     ;
2011   *p = new string_list(s);
2012 }
2013
2014 void path::set_ypath(path *p)
2015 {
2016   ypath = p;
2017 }
2018
2019 // return non-zero for success
2020
2021 int path::follow(const place &pl, place *result) const
2022 {
2023   if (is_position) {
2024     result->x = pos.x;
2025     result->y = pos.y;
2026     result->obj = 0;
2027     return 1;
2028   }
2029   const place *p = &pl;
2030   for (string_list *lb = label_list; lb; lb = lb->next)
2031     if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) {
2032       lex_error("object does not contain a place `%1'", lb->str);
2033       return 0;
2034     }
2035   if (crn == 0 || p->obj == 0)
2036     *result = *p;
2037   else {
2038     position ps = ((p->obj)->*(crn))();
2039     result->x = ps.x;
2040     result->y = ps.y;
2041     result->obj = 0;
2042   }
2043   if (ypath) {
2044     place tem;
2045     if (!ypath->follow(pl, &tem))
2046       return 0;
2047     result->y = tem.y;
2048     if (result->obj != tem.obj)
2049       result->obj = 0;
2050   }
2051   return 1;
2052 }
2053
2054 void print_object_list(object *p)
2055 {
2056   for (; p; p = p->next) {
2057     p->print();
2058     p->print_text();
2059   }
2060 }
2061
2062 void print_picture(object *obj)
2063 {
2064   bounding_box bb;
2065   for (object *p = obj; p; p = p->next)
2066     p->update_bounding_box(&bb);
2067   double scale;
2068   lookup_variable("scale", &scale);
2069   out->start_picture(scale, bb.ll, bb.ur);
2070   print_object_list(obj);
2071   out->finish_picture();
2072 }
2073