/*@z15.c:Size Constraints:MinConstraint(), EnlargeToConstraint()@*************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.42)                       */
/*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
/*                                                                           */
/*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
/*  School of Information Technologies                                       */
/*  The University of Sydney 2006                                            */
/*  AUSTRALIA                                                                */
/*                                                                           */
/*  This program is free software; you can redistribute it and/or modify     */
/*  it under the terms of the GNU General Public License as published by     */
/*  the Free Software Foundation; either Version 3, or (at your option)      */
/*  any later version.                                                       */
/*                                                                           */
/*  This program is distributed in the hope that it will be useful,          */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/*  GNU General Public License for more details.                             */
/*                                                                           */
/*  You should have received a copy of the GNU General Public License        */
/*  along with this program; if not, write to the Free Software              */
/*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
/*                                                                           */
/*  FILE:         z15.c                                                      */
/*  MODULE:       Size Constraints                                           */
/*  EXTERNS:      MinConstraint(), EnlargeToConstraint(),                    */
/*                ReflectConstraint(), SemiRotateConstraint(),               */
/*                RotateConstraint(), InvScaleConstraint(), Constrained(),   */
/*                EchoConstraint(), DebugConstrained()                       */
/*                                                                           */
/*****************************************************************************/
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#include "externs.h"


/*****************************************************************************/
/*                                                                           */
/*  MinConstraint(xc, yc)                                                    */
/*                                                                           */
/*  Replace *xc by the minimum of the two constraints *xc and *yc.           */
/*                                                                           */
/*****************************************************************************/

void MinConstraint(CONSTRAINT *xc, CONSTRAINT *yc)
{ bc(*xc)  = find_min(bc(*xc),  bc(*yc));
  bfc(*xc) = find_min(bfc(*xc), bfc(*yc));
  fc(*xc)  = find_min(fc(*xc),  fc(*yc));
} /* end MinConstraint */


/*****************************************************************************/
/*                                                                           */
/*  SetSizeToMaxForwardConstraint(b, f, c)                                   */
/*                                                                           */
/*  Set *b, *f to their largest possible value within constraint *c, such    */
/*  that *f is as large as possible.                                         */
/*                                                                           */
/*****************************************************************************/

void SetSizeToMaxForwardConstraint(FULL_LENGTH *b, FULL_LENGTH *f, CONSTRAINT *c)
{
  *f = find_min(bfc(*c), fc(*c));
  *b = find_min(bc(*c), bfc(*c) - *f);
} /* end EnlargeToConstraint */


/*****************************************************************************/
/*                                                                           */
/*  EnlargeToConstraint(b, f, c)                                             */
/*                                                                           */
/*  Enlarge *b,*f to its largest possible value within constraint *c.        */
/*                                                                           */
/*****************************************************************************/

void EnlargeToConstraint(FULL_LENGTH *b, FULL_LENGTH *f, CONSTRAINT *c)
{
  *f = find_min(bfc(*c) - *b, fc(*c));
} /* end EnlargeToConstraint */


/*****************************************************************************/
/*                                                                           */
/*  ReflectConstraint(xc, yc)                                                */
/*                                                                           */
/*  Set xc to the constraint which is yc with its back and forward reversed. */
/*                                                                           */
/*****************************************************************************/

#define ReflectConstraint(xc, yc)  SetConstraint(xc, fc(yc), bfc(yc), bc(yc))


/*@::ScaleToConstraint(), InvScaleConstraint(), etc@**************************/
/*                                                                           */
/*  int ScaleToConstraint(b, f, c)                                           */
/*                                                                           */
/*  Return the scale factor needed to scale object of size b, f down so it   */
/*  has a size which fits tightly into constraint c.                         */
/*                                                                           */
/*****************************************************************************/

int ScaleToConstraint(FULL_LENGTH b, FULL_LENGTH f, CONSTRAINT *c)
{ float scale_factor;  int res;
  debug3(DSC, DD, "ScaleToConstraint(%s, %s, %s)", EchoLength(b),
    EchoLength(f), EchoConstraint(c));
  scale_factor = 1.0;
  if( b     > 0 )  scale_factor = find_min(scale_factor, (float) bc(*c)/b       );
  if( b + f > 0 )  scale_factor = find_min(scale_factor, (float) bfc(*c)/(b + f));
  if(     f > 0 )  scale_factor = find_min(scale_factor, (float) fc(*c)/f       );
  res = scale_factor * SF;
  debug2(DSC, DD, "ScaleToConstraint returning %.2f (%d)", scale_factor, res);
  return res;
} /* end ScaleToConstraint */


/*****************************************************************************/
/*                                                                           */
/*  InvScaleConstraint(yc, sf, xc)                                           */
/*                                                                           */
/*  Scale constraint xc to the inverse of the scale factor sf.               */
/*                                                                           */
/*****************************************************************************/

void InvScaleConstraint(CONSTRAINT *yc, FULL_LENGTH sf, CONSTRAINT *xc)
{
#if DEBUG_ON
  char buff[10];
#endif
  ifdebug(DSC, DD, sprintf(buff, "%.3f", (float) sf / SF));
  debug2(DSC, DD, "InvScaleConstraint(yc, %s, %s)", buff, EchoConstraint(xc));
  assert( sf > 0, "InvScaleConstraint: sf <= 0!" );
  bc(*yc)  = bc(*xc)  == MAX_FULL_LENGTH ? MAX_FULL_LENGTH :
    find_min(MAX_FULL_LENGTH, bc(*xc) * SF / sf);
  bfc(*yc) = bfc(*xc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH :
    find_min(MAX_FULL_LENGTH, bfc(*xc)* SF / sf);
  fc(*yc)  = fc(*xc)  == MAX_FULL_LENGTH ? MAX_FULL_LENGTH :
    find_min(MAX_FULL_LENGTH, fc(*xc) * SF / sf);
  debug1(DSC, DD, "InvScaleConstraint returning %s", EchoConstraint(yc));
} /* end InvScaleConstraint */


/*****************************************************************************/
/*                                                                           */
/*  static SemiRotateConstraint(xc, u, v, angle, yc)                         */
/*                                                                           */
/*  Used by RotateConstraint to calculate one rotated constraint.            */
/*                                                                           */
/*****************************************************************************/

static void SemiRotateConstraint(CONSTRAINT *xc, FULL_LENGTH u, FULL_LENGTH v,
float angle, CONSTRAINT *yc)
{ float cs, sn;
#if DEBUG_ON
  char buff[20];
#endif
  ifdebug(DSC, DD, sprintf(buff, "%.1f", angle * 360.0 / (2 * M_PI)));
  debug4(DSC, DD, "SemiRotateConstraint(xc, %s, %s, %sd, %s",
    EchoLength(u), EchoLength(v), buff, EchoConstraint(yc));
  cs = cos(angle);  sn = sin(angle);
  if( fabs(cs) < 1e-6 )
    SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
  else
    SetConstraint(*xc,
      find_min(MAX_FULL_LENGTH, (bc(*yc) - u * sn) / cs),
      find_min(MAX_FULL_LENGTH, (bfc(*yc) - u * sn - v * sn) / cs),
      find_min(MAX_FULL_LENGTH, (fc(*yc) - v * sn) / cs ));
  debug1(DSC, DD, "SemiRotateConstraint returning %s", EchoConstraint(xc));
} /* end SemiRotateConstraint */


/*@::RotateConstraint()@******************************************************/
/*                                                                           */
/*  RotateConstraint(c, y, angle, hc, vc, dim)                               */
/*                                                                           */
/*  Take the object angle @Rotate y, which is supposed to be constrained     */
/*  horizontally by hc and vertically by vc, and determine a constraint      */
/*  (either horizontal or vertical, depending on dim) for y.                 */
/*                                                                           */
/*  The constraint returned is a trigonometric function of all these         */
/*  parameters, including the present size of y in dimension 1-dim.          */
/*                                                                           */
/*****************************************************************************/

void RotateConstraint(CONSTRAINT *c, OBJECT y, FULL_LENGTH angle,
CONSTRAINT *hc, CONSTRAINT *vc, int dim)
{ CONSTRAINT c1, c2, c3, dc;  float theta, psi;
#if DEBUG_ON
  char buff[20];
#endif
  ifdebug(DSC, DD, sprintf(buff, "%.1f", (float) angle / DG ));
  debug4(DSC, DD, "RotateConstraint(c, y, %sd, %s, %s, %s)",
	buff, EchoConstraint(hc), EchoConstraint(vc), dimen(dim));

  /* work out angle in radians between 0 and 2*M_PI */
  theta = (float) angle * 2 * M_PI / (float) (DG * 360);
  while( theta < 0 ) theta += 2 * M_PI;
  while( theta >= 2 * M_PI ) theta -= 2 * M_PI;
  assert( 0 <= theta && theta <= 2 * M_PI, "RotateConstraint: theta!" );

  /* determine theta, c1, and c2 depending on which quadrant we are in */
  if( theta <= M_PI / 2.0 )   /* first quadrant */
  { theta = theta;
    CopyConstraint(c1, *hc);
    CopyConstraint(c2, *vc);
  }
  else if ( theta <= M_PI )   /* second quadrant */
  { theta -= M_PI / 2.0;
    ReflectConstraint(c1, *vc);
    CopyConstraint(c2, *hc);
  }
  else if ( theta <= 3.0 * M_PI / 2.0 )   /* third quadrant */
  { theta -= M_PI;
    ReflectConstraint(c1, *hc);
    ReflectConstraint(c2, *vc);
  }
  else /* fourth quadrant */
  { theta -= 3.0 * M_PI / 2.0;
    CopyConstraint(c1, *vc);
    ReflectConstraint(c2, *hc);
  }
  psi = M_PI / 2.0 - theta;
  debug2(DSC, DD, "  c1: %s;  c2: %s", EchoConstraint(&c1), EchoConstraint(&c2));

  /* return the minimum of the two constraints, rotated */
  if( dim == COLM )
  { SemiRotateConstraint(c, back(y, ROWM), fwd(y, ROWM), theta, &c1);
    ReflectConstraint(c3, c2);
    SemiRotateConstraint(&dc, fwd(y, ROWM), back(y, ROWM), psi, &c3);
    MinConstraint(c, &dc);
  }
  else
  { SemiRotateConstraint(c, back(y, COLM), fwd(y, COLM), psi, &c1);
    SemiRotateConstraint(&dc, fwd(y, COLM), back(y, COLM), theta, &c2);
    MinConstraint(c, &dc);
  }

  debug1(DSC, DD, "RotateConstraint returning %s", EchoConstraint(c));
} /* end RotateConstraint */

/*@::InsertScale()@***********************************************************/
/*                                                                           */
/*  BOOLEAN InsertScale(x, c)                                                */
/*                                                                           */
/*  Insert a @Scale object above x so that x is scaled horizontally to fit   */
/*  constraint c.  If this is not possible, owing to the necessary scale     */
/*  factor being too small, then don't do it; return FALSE instead.          */
/*                                                                           */
/*****************************************************************************/

BOOLEAN InsertScale(OBJECT x, CONSTRAINT *c)
{ int scale_factor; OBJECT prnt;
  scale_factor = ScaleToConstraint(back(x, COLM), fwd(x, COLM), c);
  if( scale_factor >= 0.2 * SF )
  {
    New(prnt, SCALE);
    underline(prnt) = underline(x);
    FposCopy(fpos(prnt), fpos(x));

    /* set horizontal size and scale factor */
    bc(constraint(prnt)) = scale_factor;
    back(prnt, COLM) = ( back(x, COLM) * scale_factor ) / SF;

    /* *** slightly too small?
    fwd(prnt,  COLM) = ( fwd(x,  COLM) * scale_factor ) / SF;
    *** */
    fwd(prnt,  COLM) = find_min(bfc(*c) - back(prnt, COLM), fc(*c));

    /* set vertical size and scale factor */
    fc(constraint(prnt)) = 1 * SF;
    back(prnt, ROWM) = back(x, ROWM);
    fwd(prnt, ROWM) = fwd(x, ROWM);

    /* link prnt above x and return */
    ReplaceNode(prnt, x);
    Link(prnt, x);
    return TRUE;
  }
  else return FALSE;
} /* end InsertScale */


/*@::CatConstrained()@********************************************************/
/*                                                                           */
/*  static CatConstrained(x, xc, ratm, y, dim, OBJECT *why)                  */
/*                                                                           */
/*  Calculate the size constraint of object x, as for Constrained below.     */
/*  y is the enclosing VCAT etc. object;  ratm is TRUE if a ^ lies after     */
/*  x anywhere.  dim is COLM or ROWM.                                        */
/*                                                                           */
/*  The meaning of the key variables is as follows:                          */
/*                                                                           */
/*  be       The amount by which back(x, dim) can increase from zero         */
/*           without having any impact on size(y, dim).  Thereafter,         */
/*           any increase causes an equal increase in size(y, dim).          */
/*                                                                           */
/*  fe       The amount by which fwd(x, dim) can increase from zero          */
/*           without having any impact on size(y, dim).  Thereafter,         */
/*           any increase causes an equal increase in size(y, dim).          */
/*                                                                           */
/*  backy,   The value that back(y, dim) and fwd(y, dim) would have if x     */
/*  fwdy     was definite with size 0,0.  They will in general be larger     */
/*           than the present values if x is indefinite, and smaller         */
/*           if x is definite, although it depends on marks and gaps.        */
/*                                                                           */
/*****************************************************************************/

static void CatConstrained(OBJECT x, CONSTRAINT *xc, BOOLEAN ratm,
OBJECT y, int dim, OBJECT *why)
{ int side;			/* the size of y that x is on: BACK, ON, FWD */
  CONSTRAINT yc;		/* constraints on y                          */
  FULL_LENGTH backy=0, fwdy=0;	/* back(y), fwd(y) would be if x was (0, 0)  */
  FULL_LENGTH be, fe;		/* amount back(x), fwd(x) can be for free    */
  FULL_LENGTH beffect, feffect;	/* scratch variables for calculations        */
  FULL_LENGTH seffect;		/* scratch variables for calculations        */
  OBJECT link, sg, pg;	/* link to x, its successor and predecessor  */
  OBJECT prec_def, sd;	/* definite object preceding (succeeding) x  */
  int tb, tbf, tf, tbc, tbfc, tfc, mxy, myz;

  Constrained(y, &yc, dim, why);
  if( constrained(yc) )
  {
    /* find the link of x, and its neighbours and their links */
    link = UpDim(x, dim);
    SetNeighbours(link, ratm, &pg, &prec_def, &sg, &sd, &side);

    /* amount of space available at x without changing the size of y */
    be = pg == nilobj ? 0 : ExtraGap(fwd(prec_def, dim), 0, &gap(pg), BACK);
    fe = sg == nilobj ? 0 : ExtraGap(0, back(sd, dim),      &gap(sg), FWD);

    if( is_indefinite(type(x)) )
    {
      /* insert two lengths and delete one */
      beffect = pg==nilobj ? 0 : MinGap(fwd(prec_def, dim), 0, 0, &gap(pg));
      feffect = sg==nilobj ? 0 : MinGap(0, back(sd,dim), fwd(sd,dim), &gap(sg));
      seffect = pg==nilobj ?
	  sg == nilobj ? 0 : back(sd, dim) :
	  sg == nilobj ? fwd(prec_def, dim) :
	    MinGap(fwd(prec_def, dim), back(sd, dim), fwd(sd, dim), &gap(sg));

      switch( side )
      {
	case BACK:	backy = back(y, dim) + beffect + feffect - seffect;
			fwdy  = fwd(y, dim);
			break;

	case ON:	/* must be first, other cases prohibited */
			backy = 0;
			fwdy = fwd(y, dim) + feffect;
			break;

	case FWD:	backy = back(y, dim);
			fwdy  = fwd(y, dim) + beffect + feffect - seffect;
			break;
      }
    }

    else /* x is definite */

    { beffect = pg == nilobj ? back(x, dim) :
	MinGap(fwd(prec_def, dim), back(x,dim), fwd(x,dim), &gap(pg)) -
	MinGap(fwd(prec_def, dim), 0,           0,          &gap(pg));

      feffect = sg == nilobj ? fwd(x, dim) :
	MinGap(fwd(x, dim), back(sd, dim), fwd(sd, dim), &gap(sg)) -
	MinGap(0,           back(sd, dim), fwd(sd, dim), &gap(sg));

      switch( side )
      {
	case BACK:	backy = back(y, dim) - beffect - feffect;
			fwdy  = fwd(y, dim);
			break;

	case ON:	backy = back(y, dim) - beffect;
			fwdy  = fwd(y, dim)  - feffect;
			break;

	case FWD:	backy = back(y, dim);
			fwdy  = fwd(y, dim) - beffect - feffect;
			break;
      }
    }

    debug5(DSC, DD, "  side: %s, backy: %s, fwdy: %s, be: %s, fe: %s",
		Image(side), EchoLength(backy), EchoLength(fwdy),
		EchoLength(be), EchoLength(fe) );

    if( !FitsConstraint(backy, fwdy, yc) )
      SetConstraint(*xc, -1, -1, -1);
    else switch( side )
    {

      case BACK:
	
	tbc = bc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bc(yc) - backy;
	tbfc = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - backy - fwdy;
	mxy = find_min(tbc, tbfc);
	tb  = find_min(MAX_FULL_LENGTH, be + mxy);
	tbf = find_min(MAX_FULL_LENGTH, be + fe + mxy);
	tf  = find_min(MAX_FULL_LENGTH, fe + mxy);
	SetConstraint(*xc, tb, tbf, tf);
	break;


      case ON:
	
	tbc = bc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bc(yc) - backy;
	tbfc = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - backy - fwdy;
	tfc = fc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : fc(yc) - fwdy;
	mxy = find_min(tbc, tbfc);
	myz = find_min(tfc, tbfc);
	tb  = find_min(MAX_FULL_LENGTH, be + mxy);
	tbf = find_min(MAX_FULL_LENGTH, be + fe + tbfc);
	tf  = find_min(MAX_FULL_LENGTH, fe + myz);
	SetConstraint(*xc, tb, tbf, tf);
	break;
	

      case FWD:

	tfc = fc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : fc(yc) - fwdy;
	tbfc = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - backy - fwdy;
	mxy = find_min(tfc, tbfc);
	tb  = find_min(MAX_FULL_LENGTH, be + mxy);
	tbf = find_min(MAX_FULL_LENGTH, be + fe + mxy);
	tf  = find_min(MAX_FULL_LENGTH, fe + mxy);
	SetConstraint(*xc, tb, tbf, tf);
	break;
	
    }
  } /* end if( constrained ) */
  else SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
} /* end CatConstrained */


/*@::Constrained()@***********************************************************/
/*                                                                           */
/*  Constrained(x, xc, dim, why)                                             */
/*                                                                           */
/*  Calculate the size constraint of object x, and return it in *xc.         */
/*                                                                           */
/*  If the resulting constraint is a hard one caused by coming up against    */
/*  a HIGH (vertical) or WIDE (horizontal), set *why to this object; if      */
/*  not, leave *why unchanged.                                               */
/*                                                                           */
/*****************************************************************************/

void Constrained(OBJECT x, CONSTRAINT *xc, int dim, OBJECT *why)
{ OBJECT y, link, lp, rp, z, tlink, g;  CONSTRAINT yc, hc, vc;
  BOOLEAN ratm;  FULL_LENGTH xback, xfwd;  int tb, tf, tbf, tbc, tfc;
  SetLengthDim(dim);
  debug2(DSC, DD, "[ Constrained(%s, xc, %s, why), x =",
    Image(type(x)), dimen(dim));
  ifdebug(DSC, DD, DebugObject(x));
  assert( Up(x) != x, "Constrained: x has no parent!" );

  /* a CLOSURE which is external_ver is unconstrained in the ROWM direction */
  /* a CLOSURE which is external_hor is unconstrained in both directions   */
  if( type(x) == CLOSURE && ((dim==ROWM && external_ver(x)) || external_hor(x)) )
  {
    SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
    debug1(DSC, DD, "] Constrained returning %s (external)",EchoConstraint(xc));
    return;
  }

  /* find y, the parent of x */
  link = UpDim(x, dim);  ratm = FALSE;
  for( tlink = NextDown(link);  type(tlink) == LINK;  tlink = NextDown(tlink) )
  { Child(g, tlink);
    if( type(g) == GAP_OBJ && mark(gap(g)) )  ratm = TRUE;
  }
  y = tlink;
  debug1(DSC, DDD, "parent y = %s", Image(type(y)));
  ifdebug(DSC, DDD, DebugObject(y));

  switch( type(y) )
  {
    case PLAIN_GRAPHIC:
    case GRAPHIC:
    case LINK_SOURCE:
    case LINK_DEST:
    case LINK_DEST_NULL:
    case LINK_URL:
    case KERN_SHRINK:
    case BEGIN_HEADER:
    case SET_HEADER:
    case ONE_COL:
    case ONE_ROW:
    case HCONTRACT:
    case VCONTRACT:
    case HEXPAND:
    case VEXPAND:
    case START_HVSPAN:
    case START_HSPAN:
    case START_VSPAN:
    case SPLIT:
    case BACKGROUND:

      Constrained(y, xc, dim, why);
      break;


    case HMIRROR:
    case VMIRROR:
    
      if( (dim == COLM) == (type(y) == HMIRROR) )
      {
        Constrained(y, &yc, dim, why);
	FlipConstraint(*xc, yc);
      }
      else
        Constrained(y, xc, dim, why);
      break;


    case HSCALE:
    case VSCALE:
    
      if( (dim == COLM) != (type(y) == HSCALE) )  Constrained(y, xc, dim, why);
      else SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
      break;


    case HCOVER:
    case VCOVER:
    
      /* dubious, but not likely to arise anyway */
      if( (dim == COLM) != (type(y) == HCOVER) )  Constrained(y, xc, dim, why);
      else SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
      break;


    case SCALE:

      Constrained(y, &yc, dim, why);
      if( dim == COLM && bc(constraint(y)) == 0 )
      {
	/* Lout-supplied factor required later, could be tiny */
	SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
      }
      else
      { InvScaleConstraint(xc,
	  dim == COLM ? bc(constraint(y)) : fc(constraint(y)), &yc);
      }
      break;


    case ROTATE:
    
      Constrained(y, &hc, COLM, why);  Constrained(y, &vc, ROWM, why);
      RotateConstraint(xc, x, sparec(constraint(y)), &hc, &vc, dim);
      break;


    case WIDE:
    case HIGH:
    
      Constrained(y, xc, dim, why);
      if( (type(y)==WIDE) == (dim==COLM) )
      { MinConstraint(xc, &constraint(y));
	*why = y;
      }
      break;


    case HLIMITED:
    case VLIMITED:

      if( (type(y) == HLIMITED) == (dim == COLM) )
      {
	BOOLEAN still_searching = TRUE;
	z = y;
	SetConstraint(*xc, back(z, dim), size(z, dim), fwd(z, dim));
	debug2(DSC, D, "  [ %s (%s)", Image(type(z)), EchoConstraint(xc));
	while( still_searching && Up(z) != z )
	{
          Parent(z, UpDim(z, dim));
	  switch( type(z) )
	  {
	    case VLIMITED:
	    case HLIMITED:
	    case COL_THR:
	    case ROW_THR:
	    case ONE_COL:
	    case ONE_ROW:
	    case HCONTRACT:
	    case VCONTRACT:
	    case SPLIT:
	    case START_VSPAN:
	    case START_HSPAN:

	      SetConstraint(*xc, back(z, dim), size(z, dim), fwd(z, dim));
	      debug2(DSC, DD, "    let s = %s (%s)", Image(type(z)),
	        EchoConstraint(xc));
	      break;


	    case HSPANNER:
	    case VSPANNER:

	      /* SpannerAvailableSpace(z, dim, &b, &f); */
	      CopyConstraint(*xc, constraint(z));
	      debug2(DSC, D, "  ] let s = %s (%s) and stop",
		Image(type(z)), EchoConstraint(&constraint(z)));
	      still_searching = FALSE;
	      break;


	    default:

	      debug1(DSC, D, "  ] stopping at %s", Image(type(z)));
	      still_searching = FALSE;
	      break;
	  }
	}
	*why = y;
      }
      else
      {
        Constrained(y, xc, dim, why);
      }
      break;


    case VSPANNER:
    case HSPANNER:

      /* we're saying that a spanner has a fixed constraint that is */
      /* determined just once in its life                           */
      CopyConstraint(*xc, constraint(y));
      debug2(DSC, DD, "  Constrained(%s) = %s", Image(type(y)), EchoConstraint(xc));
      /* SetConstraint(*xc, back(y, dim), size(y, dim), fwd(y, dim)); */
      break;


    case HSHIFT:
    case VSHIFT:

      if( (type(y) == HSHIFT) == (dim == COLM) )
      { Constrained(y, &yc, dim, why);
	tf = FindShift(y, x, dim);
	SetConstraint(*xc,
	  find_min(bc(yc), bfc(yc)) - tf, bfc(yc), find_min(fc(yc), bfc(yc)) + tf);
      }
      else Constrained(y, xc, dim, why);
      break;


    case HEAD:
    
      if( dim == ROWM )
	SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
      else
      {	CopyConstraint(yc, constraint(y));
	debug1(DSC, DD, "  head: %s; val is:", EchoConstraint(&yc));
	ifdebug(DSC, DD, DebugObject(y));
	goto REST_OF_HEAD;   /* a few lines down */
      }
      break;


    case COL_THR:
    case ROW_THR:

      assert( (type(y)==COL_THR) == (dim==COLM), "Constrained: COL_THR!" );
      Constrained(y, &yc, dim, why);
      tb = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - fwd(y, dim);
      tb = find_min(bc(yc), tb);
      tf = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - back(y, dim);
      tf = find_min(fc(yc), tf);
      SetConstraint(*xc, tb, bfc(yc), tf);
      break;


    case VCAT:
    case HCAT:
    case ACAT:
    
      if( (type(y)==VCAT) == (dim==ROWM) )
      {	CatConstrained(x, xc, ratm, y, dim, why);
	break;
      }
      Constrained(y, &yc, dim, why);
      if( !constrained(yc) )
	SetConstraint(*xc, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
      else
      {
	REST_OF_HEAD:
	/* let lp and rp be the links of the gaps delimiting */
	/* the components joined to x (or parent if no such) */
	for( lp = PrevDown(link);  lp != y;  lp = PrevDown(lp) )
	{ Child(z, lp);
	  if( type(z) == GAP_OBJ && !join(gap(z)) )  break;
	}
	for( rp = NextDown(link);  rp != y;  rp = NextDown(rp) )
	{ Child(z, rp);
	  if( type(z) == GAP_OBJ && !join(gap(z)) )  break;
	}
	if( lp == y && rp == y && !(type(y) == HEAD && seen_nojoin(y)) )
	{
	  /* if whole object is joined, do this */
          tb = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - fwd(y, dim);
          tb = find_min(bc(yc), tb);
          tf = bfc(yc) == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : bfc(yc) - back(y, dim);
          tf = find_min(fc(yc), tf);
          SetConstraint(*xc, tb, bfc(yc), tf);
	}
	else
	{
	  /* if // or || is present, do this */
	  xback = xfwd = 0;
	  for(link = NextDown(lp); link != rp;  link = NextDown(link) )
	  { Child(z, link);
	    if( type(z) == GAP_OBJ || is_index(type(z)) )  continue;
	    xback = find_max(xback, back(z, dim));
	    xfwd = find_max(xfwd, fwd(z, dim));
	  }
	  debug2(DSC, DD, "  lp != rp; xback,xfwd = %s,%s",
			EchoLength(xback), EchoLength(xfwd));
	  tbf = find_min(bfc(yc), fc(yc));
	  tbc = tbf == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : tbf - xfwd;
	  tfc = tbf == MAX_FULL_LENGTH ? MAX_FULL_LENGTH : tbf - xback;
	  SetConstraint(*xc, tbc, tbf, tfc);
	}
      }
      break;


    default:
    
      assert1(FALSE, "Constrained:", Image(type(y)));
      break;

  }
  debug2(DSC, DD, "] Constrained %s returning %s", Image(type(x)),
    EchoConstraint(xc));
} /* end Constrained */


/*@::EchoConstraint(), DebugConstrained()@************************************/
/*                                                                           */
/*  FULL_CHAR *EchoConstraint(c)                                             */
/*                                                                           */
/*  Returns a string showing constraint *c, in centimetres.                  */
/*                                                                           */
/*****************************************************************************/
#if DEBUG_ON

FULL_CHAR *EchoConstraint(CONSTRAINT *c)
{ static char str[2][40];
  static int i = 0;
  i = (i+1) % 2;
  sprintf(str[i], "<%s, %s, %s>", EchoLength(bc(*c)), EchoLength(bfc(*c)),
    EchoLength(fc(*c)));
  return AsciiToFull(str[i]);
} /* end EchoConstraint */


/*****************************************************************************/
/*                                                                           */
/*  DebugConstrained(x)                                                      */
/*                                                                           */
/*  Calculate and print the constraints of all closures lying within         */
/*  sized object x.                                                          */
/*                                                                           */
/*****************************************************************************/

void DebugConstrained(OBJECT x)
{ OBJECT y, link, why;
  CONSTRAINT c;
  debug1(DSC, DDD, "DebugConstrained( %s )", EchoObject(x) );
  switch( type(x) )
  {

    case CROSS:
    case FORCE_CROSS:
    case ROTATE:
    case BACKGROUND:
    case INCGRAPHIC:
    case SINCGRAPHIC:
    case PLAIN_GRAPHIC:
    case GRAPHIC:
    case LINK_SOURCE:
    case LINK_DEST:
    case LINK_DEST_NULL:
    case LINK_URL:
    case KERN_SHRINK:
    case WORD:
    case QWORD:
    case START_HVSPAN:
    case START_HSPAN:
    case START_VSPAN:
    case HSPAN:
    case VSPAN:
    
      break;


    case CLOSURE:
    
      Constrained(x, &c, COLM, &why);
      debug2(DSC, DD, "Constrained( %s, &c, COLM ) = %s",
	EchoObject(x), EchoConstraint(&c));
      Constrained(x, &c, ROWM, &why);
      debug2(DSC, DD, "Constrained( %s, &c, ROWM ) = %s",
	EchoObject(x), EchoConstraint(&c));
      break;


    case SPLIT:
    
      link = DownDim(x, COLM);  Child(y, link);
      DebugConstrained(y);
      break;


    case HEAD:
    case ONE_COL:
    case ONE_ROW:
    case HCONTRACT:
    case VCONTRACT:
    case HLIMITED:
    case VLIMITED:
    case HEXPAND:
    case VEXPAND:
    case HMIRROR:
    case VMIRROR:
    case HSCALE:
    case VSCALE:
    case HCOVER:
    case VCOVER:
    case SCALE:
    case WIDE:
    case HIGH:
    
      link = Down(x);  Child(y, link);
      DebugConstrained(y);
      break;


    case COL_THR:
    case VCAT:
    case HCAT:
    case ACAT:
    
      for( link = Down(x);  link != x;  link =NextDown(link) )
      {	Child(y, link);
	if( type(y) != GAP_OBJ && !is_index(type(y)) )  DebugConstrained(y);
      }
      break;


    default:
    
      assert1(FALSE, "DebugConstrained:", Image(type(x)));
      break;

  }
  debug0(DSC, DDD, "DebugConstrained returning.");
} /* end DebugConstrained */
#endif
