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