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