% Macros for boxes


% Find the length of the prefix of string s for which cond is true for each
% character c of the prefix
vardef str_prefix(expr s)(text cond) =
  save i_, c; string c;
  i_ = 0;
  forever:
    c := substring (i_,i_+1) of s;
    exitunless cond;
    exitif incr i_=length s;
  endfor
  i_
enddef;

% Take a string returned by the str operator and return the same string
% with explicit numeric subscripts replaced by generic subscript symbols [].
vardef generisize(expr ss) =
  save res, s, l; string res, s;
  res = "";             % result so far
  s = ss;               % left to process
  forever: exitif s="";
    l := str_prefix(s, (c<>"[") and ((c<"0") or (c>"9")));
    res := res & substring (0,l) of s;
    s := substring (l,infinity) of s;
    if s<>"":
      res := res & "[]";
      l := if s>="[":  1 + str_prefix(s, c<>"]")
           else:  str_prefix(s, (c=".") or ("0"<=c) and (c<="9"))
           fi;
      s := substring(l,infinity) of s;
    fi
  endfor
  res
enddef;


% Make sure the string _n_gen_ is generisize(_n_):
vardef set_n_gen_ =
  if _n_ <> _n_cur_:
    _n_cur_:=_n_;
    _n_gen_:=generisize(_n_);
  fi
enddef;

string _n_, _n_cur_, _n_gen_;
_n_cur_ := "]";              % this won't match _n_


% Given a type t and list of variable names vars, make sure that they are
% of type t and redeclare them as necessary.  In the vars list _n represents
% scantokens _n_, a suffix that might contain numeric subscripts.
% This suffix needs to be replaced by scantokens _n_gen_ in order to get
% a variable that can be declared to be of type t.
vardef generic_declare(text t) text vars =
  set_n_gen_;
  forsuffixes v_=vars:
    if  forsuffixes _n=scantokens _n_: not t v_ endfor:
      def _gdmac_ text _n = t v_ enddef;
      expandafter _gdmac_ scantokens _n_gen_;
    fi
  endfor
enddef;

% Here is another version that redeclares the vars even if they are already
% of the right type.
vardef generic_redeclare(text t) text vars =
  set_n_gen_;
  def _gdmac_ text _n = t vars enddef;
  expandafter _gdmac_ scantokens _n_gen_;
enddef;







% pp should be a string giving the name of a macro that finds the boundary path
% sp should be a string that names a macro for fixing the size and shape
% The suffix $ is the name of the box
% The text t gives the box contents: either empty, a picture, or a string to
% typeset
def beginbox_(expr pp,sp)(suffix $)(text t) =
  _n_ := str $;
  generic_declare(pair) _n.off, _n.c;
  generic_declare(string) pproc_._n, sproc_._n;
  generic_declare(picture) pic_._n;
  pproc_$:=pp; sproc_$:=sp;
  pic_$ = nullpicture;
  for _p_=t:
    pic_$:=
      if picture _p_: _p_
      else: _p_ infont defaultfont scaled defaultscale
      fi;
  endfor
  $c = $off + .5[llcorner pic_$, urcorner pic_$]
enddef;


% The suffix cl names a vardef macro that clears box-related variables
% The suffix $ is the name of the box being ended.
def endbox_(suffix cl, $) =
  if known pic_.prevbox: _dojoin(prevbox,$); fi
  def prevbox = $ enddef;
  expandafter def expandafter clearboxes expandafter =
    clearboxes cl($);
  enddef
enddef;


% Text t gives equations for joining box a to box b.
def boxjoin(text t) =
  def prevbox=_ enddef;
  def _dojoin(suffix a,b) = t enddef;
enddef;


extra_beginfig := extra_beginfig
  & "boxjoin();save pic_,sproc_,pproc_;def clearboxes=enddef;";
extra_endfig := extra_endfig & "clearboxes;";


% Given a list of box names, give whatever default values are necessary
% in order to fix the size and shape of each box.
vardef fixsize(text t) =
  forsuffixes $=t:  scantokens sproc_$($);  endfor
enddef;


% Given a list of box names, give default values for any unknown positioning
% offsets
vardef fixpos(text t) =
  forsuffixes $=t:
    if unknown xpart $.off:  xpart $.off=0; fi
    if unknown ypart $.off:  ypart $.off=0; fi
  endfor
enddef;


% Return the boundary path for the given box
vardef bpath suffix $ =
  fixsize($); fixpos($);
  scantokens pproc_$($)
enddef;


% Return the contents of the given box.  First define a private version that
% the user can't accidently clobber.
vardef pic_mac_ suffix $ =
  fixsize($); fixpos($);
  pic_$ shifted $off
enddef;

vardef pic suffix $ = pic_mac_ $ enddef;


def drawboxed(text t) =         % Draw each box
  fixsize(t); fixpos(t);
  forsuffixes s=t: draw pic_mac_.s; draw bpath.s; endfor
enddef;

def drawunboxed(text t) =       % Draw contents of each box
  fixsize(t); fixpos(t);
  forsuffixes s=t: draw pic_mac_.s; endfor
enddef;

def drawboxes(text t) =         % Draw boundary path for each box
  forsuffixes s=t: draw bpath.s; endfor
enddef;





% Rectangular boxes

newinternal defaultdx, defaultdy;
defaultdx := defaultdy := 3bp;

vardef boxit@#(text tt) =
  beginbox_("boxpath_","sizebox_",@#,tt);
  generic_declare(pair) _n.sw, _n.s, _n.se, _n.e, _n.ne, _n.n, _n.nw, _n.w;
  0 = xpart (@#nw-@#sw) = ypart(@#se-@#sw);
  0 = xpart(@#ne-@#se) = ypart(@#ne-@#nw);
  @#w = .5[@#nw,@#sw];
  @#s = .5[@#sw,@#se];
  @#e = .5[@#ne,@#se];
  @#n = .5[@#ne,@#nw];
  @#ne-@#c = @#c-@#sw = (@#dx,@#dy) + .5*(urcorner pic_@# - llcorner pic_@#);
  endbox_(clearb_,@#);
enddef;

def boxpath_(suffix $) =
  $.sw -- $.se -- $.ne -- $.nw -- cycle
enddef;

def sizebox_(suffix $) =
  if unknown $.dx: $.dx=defaultdx; fi
  if unknown $.dy: $.dy=defaultdy; fi
enddef;

vardef clearb_(suffix $) =
  _n_ := str $;
  generic_redeclare(numeric) _n.sw, _n.s, _n.se, _n.e, _n.ne, _n.n, _n.nw, _n.w,
    _n.c, _n.off, _n.dx, _n.dy;
enddef;





% Circular and oval boxes

newinternal circmargin; circmargin:=2bp;  % default clearance for picture corner

vardef circleit@#(text tt) =
  beginbox_("thecirc_","sizecirc_",@#,tt);
  generic_declare(pair) _n.n, _n.s, _n.e, _n.w;
  @#e-@#c = @#c-@#w = (@#dx,0) + .5*(lrcorner pic_@#-llcorner pic_@#);
  @#n-@#c = @#c-@#s = (0,@#dy) + .5*(ulcorner pic_@#-llcorner pic_@#);
  endbox_(clearc_,@#);
enddef;

def thecirc_(suffix $) =
  $.e{up} ... $.n{left} ... $.w{down} ... $.s{right} ... cycle
enddef;

vardef clearc_(suffix $) =
  _n_ := str $;
  generic_redeclare(numeric) _n.n, _n.s, _n.e, _n.w, _n.c, _n.off, _n.dx, _n.dy;
enddef;

vardef sizecirc_(suffix $) =
  save a_, b_;
  (a_,b_) = .5*(urcorner pic_$ - llcorner pic_$);
  if unknown $dx:
    if unknown $dy:
      if unknown($dy-$dx): a_+$dx=b_+$dy; fi
      if a_+$dx=b_+$dy: a_+$dx = a_++b_ + circmargin;
      else: $dx =
        pathsel_(max(a_,b_+$dx-$dy), (a_+d_,0){up}...(0,b_+d_+$dy-$dx){left});
      fi
    else: $dx = pathsel_(a_, (a_+d_,0){up}...(0,b_+$dy){left});
    fi
  elseif unknown $dy:
    $dy = pathsel_(b_, (a_+$dx,0){up}...(0,b_+d_){left});
  fi
enddef;

vardef pathsel_(expr dhi)(text tt) =
  save f_, p_; path p_;
  p_ = origin..(a_,b_)+circmargin*unitvector(a_,b_);
  vardef f_(expr d_) =
    xpart((tt) intersectiontimes p_) >= 0
  enddef;
  solve f_(0,dhi+1.5circmargin)
enddef;
