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