/*@z23.c:Galley Printer:ScaleFactor()@****************************************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.43)                       */
/*  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:         z23.c                                                      */
/*  MODULE:       Galley Printer                                             */
/*  EXTERNS:      FixAndPrintObject()                                        */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"
#define	NO_SUPPRESS	FALSE
#define	SUPPRESS	TRUE
/* #define word_equal(x, str)  (is_word(type(x)) && StringEqual(string(x), str)) */


/*****************************************************************************/
/*                                                                           */
/*  FirstDefiniteLDN(x, link, y, jn, ymk, dim, sp, pg)                       */
/*  NextDefiniteWithGapLDN(x, link, y, g, jn, ymk, dim, sp, pg)              */
/*                                                                           */
/*  Like FirstDefinite and NextDefiniteWithGap but during the scan, if a     */
/*  LINK_DEST_NULL is encountered, call FixAndPrintObject on it before       */
/*  continuing on to find the next definite object.                          */
/*                                                                           */
/*****************************************************************************/

#define FirstDefiniteLDN(x, link, y, jn, ymk, dim, sp, pg)		\
{ jn = TRUE;								\
  for( link = Down(x);  link != x;  link = NextDown(link) )		\
  { Child(y, link)							\
      ;									\
    if( type(y) == GAP_OBJ )  jn = jn && join(gap(y));			\
    else if( type(y)==SPLIT ? SplitIsDefinite(y) : is_definite(type(y)))\
      break;								\
    else if( type(y) == LINK_DEST_NULL )				\
      FixAndPrintObject(y, ymk, 0, 0, dim, sp, pg, 0, &aback, &afwd);	\
  }									\
} /* end FirstDefiniteLDN */

#define NextDefiniteWithGapLDN(x, link, y, g, jn, ymk, dim, sp, pg)	\
{ g = nilobj;  jn = TRUE;						\
  for( link = NextDown(link);  link != x;  link = NextDown(link) )	\
  { Child(y, link)							\
      ;									\
    if( type(y) == GAP_OBJ )  g = y, jn = jn && join(gap(y));		\
    else if( type(y)==SPLIT ? SplitIsDefinite(y):is_definite(type(y)) )	\
    {									\
      debug2(DFS, DD, "  NextDefiniteWithGapLDN at %s %s",		\
	Image(type(y)), EchoObject(y));					\
      assert( g != nilobj, "NextDefiniteWithGap: g == nilobj!" );	\
      break;								\
    }									\
    else if( type(y) == LINK_DEST_NULL )				\
      FixAndPrintObject(y, ymk, 0, 0, dim, sp, pg, 0, &aback, &afwd);	\
  }									\
} /* end NextDefiniteWithGapLDN */



/*****************************************************************************/
/*                                                                           */
/*  static float ScaleFactor(avail_size, inner_size)                         */
/*                                                                           */
/*  Return the scale factor for this scaling, or 0 if impossible.            */
/*                                                                           */
/*****************************************************************************/

static float ScaleFactor(FULL_LENGTH avail_size, FULL_LENGTH inner_size)
{ float scale_factor;
  scale_factor = avail_size <= 0 ? 0 :
		 inner_size <= 0 ? 0 : (float) avail_size / inner_size;
  return scale_factor;
}


/*@::FindAdjustIncrement()@***************************************************/
/*                                                                           */
/*  static FULL_LENGTH FindAdjustIncrement(x, frame_size, dim)               */
/*                                                                           */
/*  Find the amount by which to increase the width of the subobjects of      */
/*  concatenation object x so that it is adjusted to fill size frame_size.   */
/*                                                                           */
/*****************************************************************************/

static FULL_LENGTH FindAdjustIncrement(OBJECT x, FULL_LENGTH frame_size,int dim)
{ OBJECT y = nilobj, link, prev = nilobj, g;
  int adjustable_gaps;  BOOLEAN jn;
  FULL_LENGTH inc, mk, actual_size;

  debug2(DGP, DD, "FindAdjustIncrement(x, %s, %s)",
	EchoLength(frame_size), dimen(dim));
  FirstDefinite(x, link, prev, jn);
  if( link != x )
  { adjustable_gaps = 0;
    mk = back(prev, dim);
    NextDefiniteWithGap(x, link, y, g, jn);
    while( link != x )
    { if ( mode(gap(g)) == TAB_MODE || units(gap(g)) == AVAIL_UNIT
				    || units(gap(g)) == FRAME_UNIT )
      {	debug0(DGP, DD, "FindAdjustIncrement returning 0 (tab gap)");
	return 0;
      }
      mk += ActualGap(fwd(prev, dim), back(y, dim), fwd(y, dim), &gap(g),
		frame_size, mk);
      prev = y;
      adjustable_gaps++;
      NextDefiniteWithGap(x, link, y, g, jn);
    }
    actual_size = mk + fwd(prev, dim);
    debug2(DGP, DD, "  actual_size = %s, adjustable_gaps = %d",
	EchoLength(actual_size), adjustable_gaps);
    inc = adjustable_gaps==0 ? 0 : (frame_size - actual_size) / adjustable_gaps;
  }
  else inc = 0;
  debug1(DGP, DD, "FindAdjustIncrement returning %s", EchoLength(inc));
  return inc;
} /* end FindAdjustIncrement */


/*@::FixAndPrintObject()@*****************************************************/
/*                                                                           */
/*  OBJECT FixAndPrintObject(x, xmk, xb, xf, dim, suppress, pg, count,       */
/*           actual_back, actual_fwd)                                        */
/*                                                                           */
/*  Fix the absolute position of object x in dimension dim, in such a way    */
/*  that the principal mark of x has coordinate xmk, and x has actual size   */
/*  (xb, xf), where usually xb >= back(x, dim) and xf >= fwd(x, dim).        */
/*                                                                           */
/*  Actually, in the case where x includes an object lying on a thread       */
/*  leading outside x, the final size of x may be different.  Because        */
/*  of this, the procedure sets *actual_back and *actual_fwd to the actual   */
/*  size of x upon return.  The caller assumes that x will exactly occupy    */
/*  this space (actual_back, actual_fwd).                                    */
/*                                                                           */
/*  The suppress parameter is true if a temporary suppression of adjustment  */
/*  in this direction is in effect (because a neighbouring adjustment has    */
/*  already been done).  This is for @HAdjust and @VAdjust, not @PAdjust.    */
/*                                                                           */
/*  If dim == COLM, the coordinate information is merely stored; but if      */
/*  dim == ROWM, it is used to generate PostScript for printing x.           */
/*                                                                           */
/*  Parameter pg records the height of the current page.  This is used       */
/*  to correct for the fact that Lout places its origin at the top left,     */
/*  while PostScript places its origin at the bottom left.  This correction  */
/*  cannot be made by transforming user space.                               */
/*                                                                           */
/*  x is child number count of its parent (used by COL_THR and ROW_THR only) */
/*                                                                           */
/*  FixAndPrintObject ordinarily returns the object passed to it; however    */
/*  it occasionally replaces that object with another, and then it is the    */
/*  replacement object that is returned.                                     */
/*                                                                           */
/*****************************************************************************/

OBJECT FixAndPrintObject(OBJECT x, FULL_LENGTH xmk, FULL_LENGTH xb,
  FULL_LENGTH xf, int dim, BOOLEAN suppress, FULL_LENGTH pg, int count,
  FULL_LENGTH *actual_back, FULL_LENGTH *actual_fwd)
{ OBJECT y = nilobj, link, prev = nilobj, g, z, face, thr, res, uplink;
  /* OBJECT fixed_thr, tmp; */
  FULL_LENGTH mk, ymk, frame_size, back_edge, yb, yf, inc, f;
  FULL_LENGTH aback, afwd;
  int i; float scale_factor;  BOOLEAN jn;
  debug8(DGP, DD, "[ FixAndPrintObject(%s %s%s, %s, %s,%s, %s, %s, pg, count)",
    Image(type(x)),
    ((type(x) == WORD || type(x) == QWORD) ? string(x) : STR_EMPTY),
    EchoFilePos(&fpos(x)),
    EchoLength(xmk), EchoLength(xb), EchoLength(xf),dimen(dim),
    (suppress == SUPPRESS ? "suppress" : "no_suppress"));
  debug2(DGP, DD, "  size(x) = %s,%s;  x =",
    EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
  ifdebug(DGP, DD, DebugObject(x));
  res = x;

  /*** start and stop debugging
  if( dim == COLM && is_word(type(x)) &&
      StringEqual(string(x), AsciiToFull("STARTBUG")) )
    dbg[DGP].on[DD] = dbg[DGP].on[D] = TRUE;
  if( dim == COLM && is_word(type(x)) &&
      StringEqual(string(x), AsciiToFull("STOPBUG")) )
    dbg[DGP].on[DD] = dbg[DGP].on[D] = FALSE;
  *** */


  switch( type(x) )
  {

    case CLOSURE:
    case NULL_CLOS:
    case PAGE_LABEL:
    case CROSS:
    case FORCE_CROSS:
    
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case START_HVSPAN:
    case START_HSPAN:
    case START_VSPAN:

      CountChild(y, DownDim(x, dim), count)
        ;
      if( type(y) == HSPANNER || type(y) == VSPANNER )
      {
        Child(z, Down(y))
          ;
	Parent(thr, UpDim(x, dim));
	save_mark(y) = xmk - back(thr, dim) + back(z, dim);

        /* do the fix now if the first column is also the last one */
	debug2(DGP, DD, "  pre-inc spanner_fixed(y) = %d, spanner_count(y) = %d",
	  spanner_fixed(y), spanner_count(y));
	if( ++spanner_fixed(y) == spanner_count(y) )
	{
	  debug6(DGP, DD, "  f+last SPAN: yf = max(%s + %s - %s, %s, %s - %s)",
	    EchoLength(xmk), EchoLength(xf), EchoLength(save_mark(y)),
	    EchoLength(fwd(z, dim)),
	    EchoLength(bfc(constraint(y))), EchoLength(back(z, dim)));
	  yf = find_max(xmk + xf - save_mark(y), fwd(z, dim));
	  yf = find_max(yf, bfc(constraint(y)) - back(z, dim));
          z = FixAndPrintObject(z, save_mark(y), back(z, dim), yf, dim,
		FALSE, pg, 1, &aback, &afwd);
	  spanner_fixed(y) = 0; /* restart for if printed again */
	}
	*actual_back = back(x, dim); *actual_fwd = fwd(x, dim);
      }
      else
      {
	debug6(DGP, DD, "%s alternate FixAndPrintObject(%s, %s, %s, %s, %s, ..)",
	  Image(type(x)), Image(type(y)), EchoLength(xmk), EchoLength(xb),
	  EchoLength(xf), dimen(dim));
        y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case HSPAN:
    case VSPAN:

      /* do the fix on the last one */
      if( (dim == COLM) == (type(x) == HSPAN) )
      {
        CountChild(y, DownDim(x, dim), count)
          ;
	assert(type(y) == HSPANNER || type(y) == VSPANNER, "FAPO HSPAN/VSPAN!");
	debug2(DGP, DD, "  pre-inc spanner_fixed(y) = %d, spanner_count(y) = %d",
	  spanner_fixed(y), spanner_count(y));
	if( ++spanner_fixed(y) == spanner_count(y) )
	{
          Child(z, Down(y))
            ;
	  debug6(DGP, DD, "  last SPAN: yf = max(%s + %s - %s, %s, %s - %s)",
	    EchoLength(xmk), EchoLength(xf), EchoLength(save_mark(y)),
	    EchoLength(fwd(z, dim)),
	    EchoLength(bfc(constraint(y))), EchoLength(back(z, dim)));
	  yf = find_max(xmk + xf - save_mark(y), fwd(z, dim));
	  yf = find_max(yf, bfc(constraint(y)) - back(z, dim));
          z = FixAndPrintObject(z, save_mark(y), back(z, dim), yf, dim,
		FALSE, pg, 1, &aback, &afwd);
	  *actual_back = back(x, dim); *actual_fwd = fwd(x, dim);
	  spanner_fixed(y) = 0; /* restart for if printed again */
	}
      }
      break;


    case WORD:
    case QWORD:
    
      if( dim == COLM )
      {
	/* save horizontal position for PrintWord below */
	word_save_mark(x) = xmk;

	/* if first occurrence of this font on this page, notify font */
	if( string(x)[0] != '\0' )
	{ face = finfo[word_font(x)].original_face;
	  if( font_page(face) < font_curr_page )
	  { debug3(DFT, DD, "FAPO: x = %s, word_font = %d, face = %s",
	      string(x), word_font(x), EchoObject(face));
	    FontPageUsed(face);
	  }
	}
      }
      else
      {
	if( string(x)[0] != '\0' )
	{ BackEnd->PrintWord(x, word_save_mark(x), pg - xmk);
	  /* NB if this word is to be underlined, it will be already enclosed
	     in an ACAT by Manifest, and that ACAT will do the underlining */
	}
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case WIDE:
    case HIGH:
    
      CountChild(y, Down(x), count)
        ;
      if( (dim == COLM) == (type(x) == WIDE) )
      { yf = bfc(constraint(x)) - back(y, dim);
        y = FixAndPrintObject(y, xmk, back(y,dim), yf, dim, NO_SUPPRESS, pg,
	      count, &aback, &afwd);
        *actual_back = xb;  *actual_fwd = xf;
      }
      else
      {	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case HSHIFT:
    case VSHIFT:

      CountChild(y, Down(x), count)
        ;
      if( (dim == COLM) == (type(x) == HSHIFT) )
      {
	/* work out the size of the shift depending on the units */
	f = FindShift(x, y, dim);
	ymk = xmk - f;
	yb = find_max(0, xb - f);
	yf = find_max(0, xf + f);
	y = FixAndPrintObject(y, ymk, yb, yf, dim, suppress, pg, count,
	      &aback, &afwd);

	/* recalculate the size of x as in MinSize */
	f = FindShift(x, y, dim);
	*actual_back = find_min(MAX_FULL_LENGTH, find_max(0, aback + f));
	*actual_fwd  = find_min(MAX_FULL_LENGTH, find_max(0, afwd  - f));
      }
      else
      {	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case HCONTRACT:
    case VCONTRACT:
    
      CountChild(y, Down(x), count)
        ;
      if( (dim == COLM) == (type(x) == HCONTRACT) )
      {	y = FixAndPrintObject(y, xmk, back(y,dim), fwd(y,dim), dim,
	  NO_SUPPRESS, pg, count, &aback, &afwd);
        *actual_back = xb;  *actual_fwd = xf;
      }
      else
      {	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case ONE_COL:
    case ONE_ROW:
    case HLIMITED:
    case VLIMITED:
    case HEXPAND:
    case VEXPAND:
    
      CountChild(y, Down(x), count)
        ;
      if( (dim == COLM) == (type(x) == ONE_COL || type(x) == HEXPAND) )
      { y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
	      &aback, &afwd);
        *actual_back = xb;  *actual_fwd = xf;
      }
      else
      {	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case HMIRROR:

      if( BackEnd->mirror_avail )
      {
	CountChild(y, Down(x), count)
	  ;
	if( dim == COLM )
	{
	  save_mark(x) = xmk;
	  y = FixAndPrintObject(y, 0, back(y, COLM), fwd(y, COLM), dim,
	    NO_SUPPRESS, pg, count, &aback, &afwd);
	}
	else
	{ BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(save_mark(x), pg - xmk);
	  BackEnd->CoordHMirror();
	  y = FixAndPrintObject(y, 0, back(y, ROWM), fwd(y, ROWM), dim,
	    NO_SUPPRESS, 0, count, &aback, &afwd);
	  BackEnd->RestoreGraphicState();
	}
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case VMIRROR:

      debug0(DRS, DD, "FixAndPrintObject at VMIRROR");
      if( BackEnd->mirror_avail )
      {
	CountChild(y, Down(x), count)
	  ;
	if( dim == COLM )
	  y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	else
	{ BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(0, pg - xmk);
	  BackEnd->CoordVMirror();
	  y = FixAndPrintObject(y, 0, back(y, ROWM), fwd(y, ROWM), dim,
	    NO_SUPPRESS, 0, count, &aback, &afwd);
	  BackEnd->RestoreGraphicState();
	}
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case VSCALE:

      debug0(DRS, DD, "FixAndPrintObject at VSCALE");
      CountChild(y, Down(x), count)
        ;
      if( BackEnd->scale_avail )
      {
	if( dim == COLM )
	  y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	else if( (scale_factor = ScaleFactor(xb+xf, size(y, ROWM))) > 0 )
	{ BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(0,
	    pg - (xmk - xb + (FULL_LENGTH) (back(y, ROWM) * scale_factor)));
	  BackEnd->CoordScale(1.0, scale_factor);
	  y = FixAndPrintObject(y, 0, back(y,ROWM), fwd(y,ROWM), dim,
	    NO_SUPPRESS, 0, count, &aback, &afwd);
	  BackEnd->RestoreGraphicState();
	}
	else if( !is_word(type(y)) || string(y)[0] != '\0' )
	  Error(23, 1, "object deleted (it cannot be scaled vertically)",
	    WARN, &fpos(x));
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case HSCALE:
    
      debug0(DRS, DD, "FixAndPrintObject at HSCALE");
      CountChild(y, Down(x), count)
        ;
      if( BackEnd->scale_avail )
      {	if( dim == COLM )
        { save_mark(x) = xmk;
	  bc(constraint(x)) = xb;
	  fc(constraint(x)) = xf;
          if( (scale_factor = ScaleFactor(xb+xf, size(y, COLM))) > 0 )
	    y = FixAndPrintObject(y, 0, back(y, COLM), fwd(y, COLM), dim,
	      NO_SUPPRESS, pg, count, &aback, &afwd);
          else if( !is_word(type(y)) || string(y)[0] != '\0' )
	    Error(23, 2, "object deleted (it cannot be scaled horizontally)",
	      WARN, &fpos(y));
        }
        else if( (scale_factor =
	  ScaleFactor(bc(constraint(x))+fc(constraint(x)),size(y,COLM))) > 0 )
        { BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(save_mark(x) - bc(constraint(x))
	       + (FULL_LENGTH) (back(y, COLM)*scale_factor), 0);
	  BackEnd->CoordScale(scale_factor, 1.0);
          y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	  BackEnd->RestoreGraphicState();
        }
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case SCALE:

      CountChild(y, Down(x), count)
        ;
      if( BackEnd->scale_avail )
      {
        if( dim == COLM )
        { assert( bc(constraint(x)) > 0, "FAPO: horizontal scale factor!" );
	  save_mark(x) = xmk;
	  yb = (xb * SF) / bc(constraint(x));
	  yf = (xf * SF) / bc(constraint(x));
          y = FixAndPrintObject(y, 0, yb, yf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	  debug6(DSF, D, "FixAndPrintObject, SCALE COLM first pass, bc(constraint(x)) %d SF %d, xb %d xf %d -> yb %d yf %d",
		bc(constraint(x)), SF, xb, xf, yb, yf);
        }
        else
        { assert( fc(constraint(x)) > 0, "FAPO: vertical scale factor!" );
	  yb = (xb * SF) / fc(constraint(x));
	  yf = (xf * SF) / fc(constraint(x));
	  BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(save_mark(x), pg - xmk);
	  BackEnd->CoordScale( (float)bc(constraint(x))/SF,
	    (float)fc(constraint(x))/SF);
          y = FixAndPrintObject(y, 0, yb, yf, dim, NO_SUPPRESS, 0, count,
		&aback, &afwd);
	  BackEnd->RestoreGraphicState();
	  debug6(DSF, D, "FixAndPrintObject, SCALE ROWM final pass, bc(constraint(x)) %d SF %d, xb %d xf %d -> yb %d yf %d",
		bc(constraint(x)), SF, xb, xf, yb, yf);
        }
      }
      else if( bc(constraint(x)) == SF && fc(constraint(x)) == SF )
      {
	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      &aback, &afwd);
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case KERN_SHRINK:

      CountChild(y, LastDown(x), count)
        ;
      if( dim == COLM )
      { y = FixAndPrintObject(y, xmk, back(y,dim), fwd(y,dim), dim,
	  NO_SUPPRESS, pg, count, &aback, &afwd);
	*actual_back = back(x, dim);  *actual_fwd = fwd(x, dim);
      }
      else
      {	y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	      actual_back, actual_fwd);
      }
      break;


    case BACKGROUND:
 
      /* this object has the size of its second child; but its first */
      /* child gets printed too, in the same space                   */
      CountChild(y, Down(x), count)
        ;
      y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	&aback, &afwd);
      CountChild(y, LastDown(x), count)
        ;
      y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
	&aback, &afwd);
      *actual_back = back(x, dim);  *actual_fwd = fwd(x, dim);
      break;


    case ROTATE:
    
      CountChild(y, Down(x), count)
        ;
      if( BackEnd->rotate_avail )
      {
        if( dim == COLM )
        { CONSTRAINT colc, rowc, yc;
          save_mark(x) = xmk;
	  SetConstraint(colc, back(x,COLM), MAX_FULL_LENGTH, fwd(x,COLM));
	  SetConstraint(rowc, back(x,ROWM), MAX_FULL_LENGTH, fwd(x,ROWM));
	  RotateConstraint(&yc, y, sparec(constraint(x)), &colc, &rowc,COLM);
	  y = FixAndPrintObject(y, 0, bc(yc), fc(yc), COLM, NO_SUPPRESS, pg,
		count, &aback, &afwd);
        }
        else
        { CONSTRAINT colc, rowc, yc;
	  BackEnd->SaveGraphicState(y);
	  BackEnd->CoordTranslate(save_mark(x), pg - xmk);
	  BackEnd->CoordRotate(sparec(constraint(x)));
	  SetConstraint(colc, back(x,COLM), MAX_FULL_LENGTH, fwd(x,COLM));
	  SetConstraint(rowc, back(x,ROWM), MAX_FULL_LENGTH, fwd(x,ROWM));
	  RotateConstraint(&yc, y, sparec(constraint(x)), &colc, &rowc, ROWM);
	  y = FixAndPrintObject(y, 0, bc(yc), fc(yc), ROWM, NO_SUPPRESS, 0,
		count, &aback, &afwd);
	  BackEnd->RestoreGraphicState();
        }
      }
      else if( sparec(constraint(x)) == 0 )
	y = FixAndPrintObject(y,xmk,xb,xf,dim,suppress,pg,count,&aback,&afwd);
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case PLAIN_GRAPHIC:

      CountChild(y, LastDown(x), count)
        ;
      if( BackEnd->plaingraphic_avail )
      {
	if( dim == COLM )
	{
	  back(x, dim) = xb;		/* NB state change here */
	  fwd(x, dim)  = xf;
	  save_mark(x) = xmk - back(x, dim);
	  debug2(DGP, DD, "PLAIN_GRAPHIC COLM storing size %s, %s",
	    EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
          y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
		&aback, &afwd);
	}
	else
	{ OBJECT tmp, pre, post;
          Child(tmp, Down(x))
            ;
          if( type(tmp) == VCAT )
          { Child(pre, Down(tmp))
              ;
            Child(post, LastDown(tmp))
              ;
          }
          else pre = tmp, post = nilobj;
	  back(x, dim) = xb;
	  fwd(x, dim)  = xf;
          BackEnd->PrintPlainGraphic(pre, save_mark(x),
	    pg - (xmk - back(x, dim)), x);
          y = FixAndPrintObject(y, xmk, xb, xf, dim, suppress, pg, count,
		&aback, &afwd);
          if( post != nilobj )
	    BackEnd->PrintPlainGraphic(post, save_mark(x),
	      pg - (xmk - back(x, dim)), x);
	}
      }
      else
	y = FixAndPrintObject(y, xmk,xb,xf,dim,suppress,pg,count,&aback,&afwd);
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case GRAPHIC:
    
      CountChild(y, LastDown(x), count)
        ;
      if( BackEnd->graphic_avail )
      {
	if( dim == COLM )
	{
	  /* if first occurrence of this font on this page, notify font */
	  if( font(save_style(x)) > 0 )
	  { face = finfo[font(save_style(x))].original_face;
	    if( font_page(face) < font_curr_page )  FontPageUsed(face);
	  }

	  back(x, dim) = xb;  /* NB state change here */
	  fwd(x, dim)  = xf;
	  debug2(DGP, DD, "GRAPHIC COLM storing size %s, %s",
	    EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
	  debug4(DSF, D, "FixAndPrintObject, GRAPHIC COLM storing size %d %d ( %s %s )",
		xb, xf, EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
	  save_mark(x) = xmk - back(x, COLM);
          y = FixAndPrintObject(y, xb, xb, xf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	}
	else
	{ OBJECT tmp, pre, post;
          Child(tmp, Down(x))
            ;
          if( type(tmp) == VCAT )
          { Child(pre, Down(tmp))
              ;
            Child(post, LastDown(tmp))
              ;
          }
          else pre = tmp, post = nilobj;
	  back(x, dim) = xb;
	  fwd(x, dim)  = xf;

	  BackEnd->SaveTranslateDefineSave(x, save_mark(x),
	    pg - (xmk + fwd(x, ROWM)));
          BackEnd->PrintGraphicObject(pre);
          BackEnd->RestoreGraphicState();
	  debug4(DSF, D, "FixAndPrintObject, GRAPHIC ROWM using size %d %d ( %s %s )",
		xb, xf, EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
          y = FixAndPrintObject(y, xb, xb, xf, dim, NO_SUPPRESS, xb + xf,
		count, &aback, &afwd);
          if( post != nilobj )  BackEnd->PrintGraphicObject(post);
          BackEnd->RestoreGraphicState();
	}
      }
      else
        y = FixAndPrintObject(y, xmk,xb,xf,dim,suppress,pg,count,&aback,&afwd);
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case LINK_SOURCE:
    case LINK_DEST:
    case LINK_DEST_NULL:
    case LINK_URL:
    
      ifdebug(DGP, D,
	Child(z, Down(x))
	  ;
	debug7(DGP, D, "[ FixAndPrintObject(%s %s%s, %s, %s, %s, %s, -)",
	Image(type(x)),
	((type(x)==LINK_DEST || type(x)==LINK_DEST_NULL) ? string(z):STR_EMPTY),
	type(x)==LINK_DEST_NULL ? " (indef)" : "",
	EchoLength(xmk), EchoLength(xb), EchoLength(xf), dimen(dim));
      );
      CountChild(y, LastDown(x), count)
        ;
      if( dim == COLM )
	save_mark(x) = xmk;
      else
      {	Child(z, Down(x))
          ;
	switch( type(x) )
	{
	  case LINK_SOURCE:

	    BackEnd->LinkSource(z, save_mark(x) - back(x, COLM),
	      (pg - xmk) - xf, save_mark(x) + fwd(x, COLM), (pg - xmk) + xb);
	    break;

	  case LINK_DEST:
	  case LINK_DEST_NULL:

	    BackEnd->LinkDest(z, save_mark(x) - back(x, COLM),
	      (pg - xmk) - xf, save_mark(x) + fwd(x, COLM), (pg - xmk) + xb);
	    break;

	  case LINK_URL:

	    BackEnd->LinkURL(z, save_mark(x) - back(x, COLM),
	      (pg - xmk) - xf, save_mark(x) + fwd(x, COLM), (pg - xmk) + xb);
	    break;
	}
      }
      y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
	    &aback, &afwd);
      *actual_back = xb;  *actual_fwd = xf;
      debug0(DGP, D, "] FixAndPrintObject returning");
      break;


    case INCGRAPHIC:
    case SINCGRAPHIC:

      CountChild(y, Down(x), count)
        ;
      if( BackEnd->incgraphic_avail )
      {
	if( dim == COLM )
	{ save_mark(x) = xmk;
	  if( incgraphic_ok(x) )
	  { debug2(DGP, DD, "  %s (style %s)",
	      EchoObject(x), EchoStyle(&save_style(x)));
	    face = finfo[font(save_style(x))].original_face;
	    if( font_page(face) < font_curr_page )
	    { debug3(DFT, DD, "FAPO-IG: x = %s, font = %d, face = %s",
		string(x), font(save_style(x)), EchoObject(face));
	      FontPageUsed(face);
	    }
	  }
	}
	else if( incgraphic_ok(x) )
	  BackEnd->PrintGraphicInclude(x, save_mark(x), pg - xmk);
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case SPLIT:
    
      link = DownDim(x, dim);
      CountChild(y, link, count)
        ;
      y = FixAndPrintObject(y, xmk, find_max(back(y, dim), xb),
	find_max(fwd(y, dim), xf), dim, suppress, pg, count,
	actual_back, actual_fwd);
      break;


    case VCAT:
    case HCAT:

      if( (type(x) == VCAT) == (dim == ROWM) )
      { 
	debug6(DGP, DD, "[ FAPO-CAT %s (%s,%s): xmk %s, xb %s, xf %s",
	    Image(type(x)), EchoLength(back(x, dim)), EchoLength(fwd(x, dim)),
	    EchoLength(xmk), EchoLength(xb), EchoLength(xf));

	FirstDefiniteLDN(x, link, prev, jn, xmk, dim, NO_SUPPRESS, pg);
	if( link != x )
	{

	  /*******************************************************************/
	  /*                                                                 */
	  /*  handle the special case of a 0rt gap at the beginning (left    */
	  /*  justify) by converting it to 0ie but increasing fwd(prev) to   */
	  /*  the maximum possible                                           */
	  /*                                                                 */
	  /*******************************************************************/

	  NextDefiniteWithGap(x, link, y, g, jn); /* not LDN since will redo */
	  if( link != x && mode(gap(g)) == TAB_MODE &&
	      units(gap(g)) == AVAIL_UNIT && width(gap(g)) == 0 )
	  {
	    debug2(DGP, DD, "  FAPO-CAT converting 0rt (back(x, dim) %s, xb %s)",
	      EchoLength(back(x, dim)), EchoLength(xb));
	    /* NB state change here */
	    fwd(prev, dim) += xb - back(x, dim);
	    back(x, dim) = xb;
	    mode(gap(g)) = EDGE_MODE;
	    units(gap(g)) = FIXED_UNIT;
	  }
	  FirstDefinite(x, link, prev, jn);  /* not LDN since already done */

	  /*******************************************************************/
	  /*                                                                 */
	  /*  Initialize the following variables:                            */
	  /*                                                                 */
	  /*  frame_size   the total width actually available                */
	  /*                                                                 */
	  /*  back_edge    where the first element begins                    */
	  /*                                                                 */
	  /*  inc          the adjust increment, used when adjusting gaps    */
	  /*                                                                 */
	  /*  mk           where the mark of prev is to go                   */
	  /*                                                                 */
	  /*******************************************************************/

	  frame_size = back(x, dim) + xf;
	  back_edge = xmk - back(x, dim);
	  if( adjust_cat(x) && !suppress )
	    inc = FindAdjustIncrement(x, frame_size, dim);
	  else inc = 0;
	  mk = back_edge + back(prev, dim);
	  debug4(DGP, DD, "  FAPO-CAT back_edge %s, mk %s, frame %s, inc %s",
	    EchoLength(back_edge), EchoLength(mk), EchoLength(frame_size),
	    EchoLength(inc));

	  /*******************************************************************/
	  /*                                                                 */
	  /*  Fix each element "prev" in turn along the cat operator         */
	  /*                                                                 */
	  /*******************************************************************/

	  NextDefiniteWithGapLDN(x, link, y, g, jn, mk, dim, NO_SUPPRESS, pg);
	  while( link != x )
	  {
	    if( mode(gap(g)) == TAB_MODE && units(gap(g)) == AVAIL_UNIT &&
		width(gap(g))==FR )
	    {
	      /* object is followed by 1rt gap, give it full space to print */
	      debug5(DGP,D,"  FAPO (a) calling FAPO(%s, %s, %s, max(%s, %s))",
	        Image(type(prev)), EchoLength(mk), EchoLength(back(prev, dim)),
		EchoLength(fwd(prev, dim)), EchoLength(xmk+xf-mk-size(y,dim)));
	      prev = FixAndPrintObject(prev, mk, back(prev, dim),
		find_max(fwd(prev, dim), xmk+xf-mk - size(y, dim)),
		dim, NO_SUPPRESS, pg, count, &aback, &afwd);
	    }
	    else
	    {
	      debug5(DGP, DD, "  FAPO-CAT (b) calling FAPO(%s, %s, %s, %s+%s)",
	        Image(type(prev)), EchoLength(mk), EchoLength(back(prev, dim)),
		EchoLength(fwd(prev, dim)), EchoLength(inc));
	      prev = FixAndPrintObject(prev, mk, back(prev, dim),
		fwd(prev, dim) + inc, dim, NO_SUPPRESS, pg, count,&aback,&afwd);
	    }
	    mk += ActualGap(afwd, back(y, dim), fwd(y, dim), &gap(g),
		    frame_size, mk - back_edge);
	    prev = y;
	    NextDefiniteWithGapLDN(x, link, y, g, jn, mk, dim, NO_SUPPRESS, pg);
	  }

	  /*******************************************************************/
	  /*                                                                 */
	  /*  At end, fix last element in conformity with "suppress"         */
	  /*  and set *actual_back and *actual_fwd.                          */
	  /*                                                                 */
	  /*******************************************************************/

	  if( suppress )
	  {
	    debug4(DGP, DD, "  FAPO-CAT (c) calling FAPO(%s, %s, %s, %s)",
	      Image(type(prev)), EchoLength(mk), EchoLength(back(prev, dim)),
	      EchoLength(fwd(prev, dim)));
	    prev = FixAndPrintObject(prev, mk, back(prev, dim), fwd(prev, dim),
	      dim, NO_SUPPRESS, pg, count, &aback, &afwd);
	  }
	  else
	  {
	    debug5(DGP, DD,"  FAPO-CAT (d) calls FAPO(%s, %s, %s, max(%s, %s))",
	      Image(type(prev)), EchoLength(mk), EchoLength(back(prev, dim)),
	      EchoLength(fwd(prev, dim)), EchoLength(xmk + xf - mk));
	    ifdebug(DGP, DD, DebugObject(prev));
	    prev = FixAndPrintObject(prev, mk, back(prev,dim),
	      find_max(fwd(prev, dim), xmk + xf - mk),
	      dim, NO_SUPPRESS, pg, count, &aback, &afwd);
	  }
	  *actual_back = find_max(back(x, dim), xb);
	  *actual_fwd = mk + fwd(prev, dim) - back_edge - *actual_back;
	  debugcond4(DGP, DD, type(x) == HCAT,
	    "HCAT original (%s, %s) to actual (%s, %s)",
	    EchoLength(back(x, dim)), EchoLength(fwd(x, dim)),
	    EchoLength(*actual_back), EchoLength(*actual_fwd));
	}
	else *actual_back = xb, *actual_fwd = xf;
	debug0(DGP, DD, "] FAPO-CAT returning.");
      }
      else
      { OBJECT start_group, zlink, m;  BOOLEAN dble_found;
	FULL_LENGTH b, f, dlen;
	start_group = nilobj;  dble_found = FALSE;  dlen = 0;
	debug0(DGP, DD, "  groups beginning.");
	FirstDefiniteLDN(x, link, y, jn, xmk, dim, NO_SUPPRESS, pg);
	if( link != x )
	{
	  /* start first group, with or without join */
	  b = back(y, dim);
	  f = fwd(y, dim);
	  m = y;
	  start_group = link;
	  dble_found = !jn;
	  debug4(DGP, DD, "  starting first group %s (%sdbl_found): b %s, f %s",
	    Image(type(y)), dble_found ? "" : "not ",
	    EchoLength(b), EchoLength(f));
	
	  NextDefiniteWithGapLDN(x, link, y, g, jn, xmk, dim, NO_SUPPRESS, pg);
	  while( link != x )
	  {
	    if( !jn )
	    {
	      /* finish off and fix the group ending just before g */
	      debug2(DGP, DD, "  finishing group: b = %s, f = %s",
		EchoLength(b), EchoLength(f));
	      m = FixAndPrintObject(m, xmk+b, b, xf-b, dim,
		NO_SUPPRESS, pg, count, &aback, &afwd);
	      b = back(m, dim);  f = fwd(m, dim);
	      for( zlink = start_group;  zlink != link;  zlink=NextDown(zlink) )
	      { CountChild(z, zlink, count)
	          ;
		if( !is_definite(type(z)) || z == m )  continue;
		z = FixAndPrintObject(z, xmk + b, b, xf - b, dim,
		  SUPPRESS, pg, count, &aback, &afwd);
		b = find_max(b, back(z, dim));  f = find_max(f, fwd(z, dim));
	      }
	      dlen = find_max(dlen, b + f);
	      dble_found = TRUE;
	      start_group = nilobj;

	      /* start new group */
	      b = back(y, dim);
	      f = fwd(y, dim);
	      m = y;
	      start_group = link;
	      debug2(DGP, DD, "  starting group: b = %s, f = %s",
		EchoLength(b), EchoLength(f));
	    }
	    else
	    {
	      /* continue with current group */
	      b = find_max(b, back(y, dim));
	      f = find_max(f, fwd(y, dim));
	      if( fwd(y, dim) > fwd(m, dim) )  m = y;
	      debug2(DGP, DD, "  continuing group: b = %s, f = %s",
		EchoLength(b), EchoLength(f));
	    }

	    NextDefiniteWithGapLDN(x, link, y, g, jn, xmk, dim, NO_SUPPRESS, pg);
	  }
	  assert( start_group != nilobj, "FAPO: final start_group!" );

	  if( dble_found || !jn )
	  {
	    /* finish off and fix this last group */
	    debug2(DGP, DD, "  finishing last group: b = %s, f = %s",
	        EchoLength(b), EchoLength(f));
	    m = FixAndPrintObject(m, xmk+b, b, xf - b, dim, NO_SUPPRESS, pg,
		  count, &aback, &afwd);
	    b = back(m, dim);  f = fwd(m, dim);
	    for( zlink = start_group;  zlink != x;  zlink = NextDown(zlink) )
	    { CountChild(z, zlink, count)
	        ;
	      if( !is_definite(type(z)) || z == m )  continue;
	      z = FixAndPrintObject(z, xmk+b, b, xf - b, dim, SUPPRESS, pg,
		    count, &aback, &afwd);
	      b = find_max(b, back(z, dim));  f = find_max(f, fwd(z, dim));
	    }
	    dlen = find_max(dlen, b + f);
	    *actual_back = 0;  *actual_fwd = dlen;
	  }
	  else
	  {
	    /* finish off and fix this last and only group */
	    debug2(DGP, DD, "  finishing last and only group: b = %s, f = %s",
	      EchoLength(b), EchoLength(f));
	    m = FixAndPrintObject(m, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
	      &b, &f);
	    for( zlink = start_group;  zlink != x;  zlink = NextDown(zlink) )
	    { CountChild(z, zlink, count)
	        ;
	      if( !is_definite(type(z)) || z == m )  continue;
	      z = FixAndPrintObject(z, xmk, xb, xf, dim, SUPPRESS, pg, count,
		    &aback, &afwd);
	      b = find_max(b, aback);  f = find_max(f, afwd);
	    }
	    *actual_back = b;  *actual_fwd = f;
	  }
	}
      }
      break;


    case ACAT:

      if( dim == COLM )
      { BOOLEAN will_adjust, adjusting;
	FULL_LENGTH actual_size, adjust_indent, frame_size, back_edge;
	FULL_LENGTH adjust_inc, inc = 0, adjust_sofar = 0;
	int adjustable_gaps, gaps_sofar = 0;
	BOOLEAN underlining; int underline_xstart = 0;
	FONT_NUM underline_font = 0;
	COLOUR_NUM underline_colour = 0;
	TEXTURE_NUM underline_texture = 0;
	OBJECT urec, last_bad_gap;
      

	/*********************************************************************/
	/*                                                                   */
	/*  The first step is to calculate the following values:             */
	/*                                                                   */
	/*    last_bad_gap     The rightmost tab gap, or nilobj if none;     */
	/*                                                                   */
	/*    adjustable_gaps  the number of gaps suitable for adjustment;   */
	/*                     i.e. to the right of the right-most tab gap,  */
	/*                     and of non-zero width;                        */
	/*                                                                   */
	/*    actual_size      the actual size of x without adjustment.      */
	/*                                                                   */
	/*  These are needed when adjusting the line.                        */
	/*                                                                   */
	/*********************************************************************/

	FirstDefinite(x, link, y, jn);  /* no LDN since this is initial pass */
	if( link == x )
	{
	  *actual_back = back(x, dim); *actual_fwd = fwd(x, dim);
	  FirstDefiniteLDN(x, link, y, jn, xmk, dim, NO_SUPPRESS, pg);
	  break;  /* no definite children, nothing to print */
	}

	/*** nasty bug finder 
	{ OBJECT ff = y;
	debugcond1(DGP, DD, word_equal(ff, "@ReportLayout"),
	  "FAPO(%s, COLM)", EchoObject(x));
	debugcond1(DGP, DD, word_equal(ff, "@ReportLayout"),
	  "  adjust_cat(x) = %s", bool_str(adjust_cat(x)));
	}
	***/

	last_bad_gap = nilobj;
	adjustable_gaps = 0;
	back_edge = xmk - xb;
	mk = back_edge + back(y, dim);
	frame_size = xb + xf;
	prev = y;
	NextDefiniteWithGap(x, link, y, g, jn);  /* no LDN, initial pass */
	while( link != x )
	{
	  save_actual_gap(g) = ActualGap(fwd(prev, dim), back(y, dim),
		fwd(y, dim), &gap(g), frame_size, mk - back_edge);
	  mk += save_actual_gap(g);
	  if( mode(gap(g)) == TAB_MODE || units(gap(g)) == AVAIL_UNIT
				       || units(gap(g)) == FRAME_UNIT )
	  { last_bad_gap = g;
	    adjustable_gaps = 0;
	  }
	  else if( width(gap(g)) > 0 )  adjustable_gaps++;
	  prev = y;
	  NextDefiniteWithGap(x, link, y, g, jn);  /* no LDN, initial pass */
	}
	actual_size = mk + fwd(prev, dim) - back_edge;

	/*********************************************************************/
	/*                                                                   */
	/*  It is possible that the line cannot be displayed in any          */
	/*  reasonable way, because the paragraph breaker was forced to      */
	/*  produce an overfull line.  In this case, actual_size will        */
	/*  exceed frame_size and there will be no adjustable gaps.  The     */
	/*  solution is to horizontally scale the line if possible, or       */
	/*  else to not print it at all.                                     */
	/*                                                                   */
	/*********************************************************************/

	if( actual_size > frame_size && adjustable_gaps == 0 )
	{ 
	  /* can't be fixed by adjustment, so scale the line or delete it */
	  CONSTRAINT c;
	  SetConstraint(c, 0, frame_size, frame_size);
	  fwd(x, dim) = actual_size;
	  debug2(DGP, DD, "  oversize, actual_size = %s, frame_size = %s",
	      EchoLength(actual_size), EchoLength(frame_size));
	  if( BackEnd->scale_avail && InsertScale(x, &c) )
	  {
	    /* the problem has just been fixed, by inserting a @Scale above x */
	    OBJECT prnt;
	    Parent(prnt, Up(x));
	    Child(y, Down(x))
	      ;
	    if( actual_size - frame_size < 1 * PT )
	    {
	      /* the correction is probably due to roundoff error, and */
	      /* anyway is too small to print an error message about   */
	    }
	    else if( Down(x) == LastDown(x) && is_word(type(y)) )
	    {
	      Error(23, 3, "word %s horizontally scaled by factor %.2f (too wide for %s paragraph)",
		WARN, &fpos(y), string(y), (float) bc(constraint(prnt)) / SF,
		EchoLength(frame_size));
	    }
	    else
	    {
	      Error(23, 4, "%s object horizontally scaled by factor %.2f (too wide for %s paragraph)",
		WARN, &fpos(x), EchoLength(size(x, COLM)),
		(float) bc(constraint(prnt)) / SF, EchoLength(frame_size));
	    }
	    prnt = FixAndPrintObject(prnt, xmk, back(prnt, dim), fwd(prnt, dim), dim,
	      NO_SUPPRESS, pg, count, &aback, &afwd);
	  }
	  else
	  {
	    /* fix the problem by refraining from printing the line */
	    if( size(x, COLM) <= 0 )
	      Error(23, 5, "oversize object has size 0 or less", INTERN, &fpos(x));
	    Child(y, Down(x))
	      ;
	    if( Down(x) == LastDown(x) && is_word(type(y)) )
	    { Error(23, 6, "word %s deleted (too wide for %s paragraph)",
		WARN, &fpos(y), string(y), EchoLength(frame_size));
	    }
	    else
	    { Error(23, 7, "%s object deleted (too wide for %s paragraph)",
		WARN, &fpos(x), EchoLength(size(x, COLM)), EchoLength(frame_size));
	    }

	    /* delete and dispose every child of x */
	    while( Down(x) != x )
	      DisposeChild(Down(x));
	    y = MakeWord(WORD, STR_EMPTY, &fpos(x));
	    Link(x, y);
	    back(y, COLM) = fwd(y, COLM) = 0;
	    back(y, ROWM) = fwd(y, ROWM) = 0;
	  }
        }
        else
        {

	  /********************************************************************/
	  /*                                                                  */
	  /*  The line may be displayed in one of four ways:  centred, right- */
	  /*  justified, adjusted, or none of the above (i.e. left justified).*/
	  /*  An overfull line is always adjusted; otherwise, the line will   */
	  /*  be centred or right justified if the display style asks for it; */
	  /*  otherwise, the line will be adjusted if adjust_cat(x) == TRUE   */
	  /*  (i.e. there is an enclosing @PAdjust) or if the display style is*/
	  /*  DO_ADJUST (meaning that this line is one of a paragraph set in  */
	  /*  the adjust or outdent break style, other than the last line);   */
	  /*  otherwise, the line is left justified.                          */
	  /*                                                                  */
	  /*  The second step is to decide which of these four cases holds    */
	  /*  for this line, and to record the decision in these variables:   */
	  /*                                                                  */
	  /*    will_adjust      TRUE if the adjusted style applies; in this  */
	  /*                     case, variables adjust_inc and inc will be   */
	  /*                     set to the appropriate adjustment value;     */
	  /*                                                                  */
	  /*    adjust_indent    If centring or right justification applies,  */
	  /*                     the indent to produce this, else zero.       */
	  /*                                                                  */
	  /*  NB adjust_inc may be negative, if the optimal paragraph breaker */
	  /*  has chosen to shrink some gaps.                                 */
	  /*                                                                  */
	  /*  NB we are assigning to adjust_cat here; is this a problem?      */
	  /*                                                                  */
	  /********************************************************************/

	  if( actual_size > frame_size )
	  { 
	    assert( adjustable_gaps > 0, "FAPO: adjustable_gaps!" );
	    adjust_cat(x) = TRUE;
	    adjust_indent = 0;
	  }
	  else switch( display_style(save_style(x)) )
	  {
	    case DO_ADJUST:	adjust_cat(x) = TRUE;
				adjust_indent = 0;
				debug1(DSF, D,  "adjust %s", EchoObject(x));
				break;
	
	    case DISPLAY_CENTRE: adjust_cat(x) = FALSE;
				adjust_indent = (frame_size - actual_size)/2;
				debug1(DGP, DD, "cdisp %s", EchoObject(x));
				break;

	    case DISPLAY_RIGHT:	adjust_cat(x) = FALSE;
				adjust_indent = frame_size - actual_size;
				debug1(DGP, DD, "rdisp %s", EchoObject(x));
				debug1(DSF, D,  "rdisp %s", EchoObject(x));
				break;

	    default:		/* leave adjust_cat(x) as is */
				adjust_indent = 0;
				break;
	  }

	  debug1(DGP, DD, "ACAT COLM %s", EchoObject(x));
	    /* EchoStyle(&save_style(x)), EchoObject(x)); */
	  debug2(DGP, DD, "frame_size = %s, actual_size = %s",
	    EchoLength(frame_size), EchoLength(actual_size));

	  if( adjust_cat(x) && adjustable_gaps > 0  )
	  { will_adjust = TRUE;
	    adjust_inc = (frame_size - actual_size) / adjustable_gaps;
	    inc = find_max(adjust_inc, 0);
	    gaps_sofar = 0;	/* number of gaps adjusted so far */
	    adjust_sofar = 0;	/* total width of adjustments so far */
	    debug2(DGP, DD,"will_adjust: adjustable_gaps = %d, adjust_inc = %s",
	      adjustable_gaps, EchoLength(adjust_inc));
	  }
	  else will_adjust = FALSE;


	  /********************************************************************/
	  /*                                                                  */
	  /*  The third and final step is to traverse x, fixing subobjects.   */
	  /*  Variable "adjusting" is true while adjusting is occurring.      */
	  /*                                                                  */
	  /********************************************************************/

	  underlining = FALSE;
	  adjusting = will_adjust && last_bad_gap == nilobj;
	  FirstDefiniteLDN(x, link, y, jn, xmk, dim, NO_SUPPRESS, pg);
	  prev = y;
	  mk = xmk - back(x, dim) + back(y, dim) + adjust_indent;
	  NextDefiniteWithGapLDN(x, link, y, g, jn, mk, dim, NO_SUPPRESS, pg);
	  while( link != x )
	  {
	    /* check for underlining */
	    if( underline(prev) == UNDER_ON )
	    {
	      debug3(DGP, DD, "  FAPO/ACAT1 underline() := %s for %s %s",
	        bool_str(FALSE), Image(type(prev)), EchoObject(prev));
	      if( !underlining )
	      {
	        /* underlining begins here */
	        underlining = TRUE;
	        debug2(DGP, DD, "underlining begins at %s %s",
		  Image(type(prev)), EchoObject(prev));
		if( is_word(type(prev)) )
		{
	          underline_font = word_font(prev);
		  underline_colour = word_underline_colour(prev);
		  underline_texture = word_texture(prev);
		}
		else
		{
	          underline_font = font(save_style(x));
		  underline_colour = underline_colour(save_style(x));
		  underline_texture = texture(save_style(x));
		}
	        underline_xstart = mk - back(prev, dim);
	      }
	      if( underline(g) == UNDER_OFF )
	      {
	        /* underlining ends here */
	        debug2(DGP, DD, "underlining ends at %s %s",
		  Image(type(prev)), EchoObject(prev));
	        New(urec, UNDER_REC);
	        back(urec, COLM) = underline_xstart;
	        fwd(urec, COLM) = mk + fwd(prev, dim);
	        word_font(urec) = underline_font;
	        word_underline_colour(urec) = underline_colour;
	        word_texture(urec) = underline_texture;
	        underlining = FALSE;
	        Link(NextDown(Up(prev)), urec);
	      }
	    }

	    /* fix previous definite now we know it is not the last one  */
	    if( adjusting && width(gap(g)) > 0 )
	    { int tmp;

	      prev = FixAndPrintObject(prev, mk, back(prev, dim),
		fwd(prev, dim) + inc, dim, NO_SUPPRESS, pg, count,&aback,&afwd);
	      gaps_sofar++;
	      tmp = ((frame_size - actual_size) * gaps_sofar) / adjustable_gaps;
	      mk += save_actual_gap(g) + (tmp - adjust_sofar);
	      adjust_sofar = tmp;
	    }
	    else
	    {
	      prev = FixAndPrintObject(prev, mk, back(prev, dim), fwd(prev,dim),
	        dim, NO_SUPPRESS, pg, count, &aback, &afwd);

	      mk += save_actual_gap(g);
	    }
	    prev = y;

	    /* commence adjustment if required */
	    if( !adjusting && will_adjust && g == last_bad_gap )
	      adjusting = TRUE;

	    NextDefiniteWithGapLDN(x, link, y, g, jn, mk, dim, NO_SUPPRESS, pg);
	  }

	  /* check for underlining */
	  debugcond3(DGP, DD, underline(prev) == UNDER_UNDEF,
	    "  underlining is UNDER_UNDEF in %s: %s %s in para:",
	    EchoFilePos(&fpos(prev)), Image(type(prev)), EchoObject(prev));
	  debugcond1(DGP, DD, underline(prev)==UNDER_UNDEF, "%s",EchoObject(x));
	  assert( underline(prev) == UNDER_OFF || underline(prev) == UNDER_ON,
	    "FixAndPrint: underline(prev)!" );
	  if( underline(prev) == UNDER_ON )
	  {
	    debug3(DGP, DD, "  FAPO/ACAT1 underline() := %s for %s %s",
	      bool_str(FALSE), Image(type(prev)), EchoObject(prev));
	    if( !underlining )
	    {
	      /* underlining begins here */
	      debug2(DGP, DD, "underlining begins at %s %s",
	        Image(type(prev)), EchoObject(prev));
	      underlining = TRUE;
	      if( is_word(type(prev)) )
	      {
	        underline_font = word_font(prev);
	        underline_colour = word_underline_colour(prev);
	        underline_texture = word_texture(prev);
	      }
	      else
	      {
	        underline_font = font(save_style(x));
	        underline_colour = underline_colour(save_style(x));
	        underline_texture = texture(save_style(x));
	      }
	      underline_xstart = mk - back(prev, dim);
	    }

	    /* underlining must end here */
	    debug2(DGP, DD, "underlining ends at %s %s",
	      Image(type(prev)), EchoObject(prev));
	    New(urec, UNDER_REC);
	    back(urec, COLM) = underline_xstart;
	    fwd(urec, COLM) = mk + fwd(prev, dim);
	    word_font(urec) = underline_font;
	    word_underline_colour(urec) = underline_colour;
	    word_texture(urec) = underline_texture;
	    underlining = FALSE;
	    Link(NextDown(Up(prev)), urec);
	  }

	  /* fix the last definite subobject, prev, which must exist */
	  prev = FixAndPrintObject(prev, mk, back(prev, dim),
	    frame_size - (mk - xmk) - back(x, dim),
	    dim, NO_SUPPRESS, pg, count, &aback, &afwd);

        }
      }
      else
      {
	debug1(DGP, DD, "ACAT ROWM %s", EchoObject(x));
	for( link = Down(x);  link != x;  link = NextDown(link) )
	{ Child(y, link)
	    ;
	  if( !is_definite(type(y)) && type(y) != LINK_DEST_NULL )
	  {
	    if( type(y) == UNDER_REC )   /* generate an underline now */
	    { BackEnd->PrintUnderline(word_font(y),word_underline_colour(y),
		word_texture(y), back(y, COLM), fwd(y, COLM), pg - xmk);
	      link = PrevDown(link);     /* remove all trace of underlining */
	      DisposeChild(Up(y));       /* in case we print this again */
	    }
	    continue;
	  }
	  y = FixAndPrintObject(y, xmk, xb, xf, dim, NO_SUPPRESS, pg, count,
		&aback, &afwd);
	}
      }
      *actual_back = xb;  *actual_fwd = xf;
      break;


    case COL_THR:
    case ROW_THR:

      assert( (type(x) == COL_THR) == (dim == COLM), "FixAndPrintObject: thr!" );
      for( link = Down(x), uplink = Up(x), i = 1;
	link != x && uplink != x && i < count;
	link = NextDown(link), uplink = NextUp(uplink), i++ )
	;
      assert( link != x && uplink != x, "FixAndPrintObject: link or uplink!" );
      CountChild(y, link, count)
        ;
      debug7(DGP, DD, "  fapo of %s (%s,%s) child %d %s (%s,%s)",
	Image(type(x)),
	EchoLength(back(x, dim)), EchoLength(fwd(x, dim)),
	i, Image(type(y)), EchoLength(back(y, dim)), EchoLength(fwd(y, dim)));

      /* This line seems to have been an optimization.  It won't
       * work if we are inside a running header, since subsequent
       * passes will have forgotten the thread.  JeffK 13/11/02
      MoveLink(uplink, link, CHILD);  DeleteLink(link);
       */

      assert( type(y) != GAP_OBJ, "FAPO: THR!");

      if( thr_state(x) != FINALSIZE )
      {	back(x, dim) = xb;  fwd(x, dim) = xf;
	thr_state(x) = FINALSIZE;
      }

      y = FixAndPrintObject(y, xmk, back(x, dim), fwd(x, dim), dim,
	NO_SUPPRESS, pg, count, &aback, &afwd);
      *actual_back = xb;  *actual_fwd = xf;
      /* if( Up(x) == x )  Dispose(x); */
      break;

      /* convert everyone to FIXED_COL_THR or FIXED_ROW_THR as appropriate */
      /* *** old code
      if( thr_state(x) == FINALSIZE )
	debug1(DGP, DD, "thr_state(%d)", (int) x);
      assert(thr_state(x) != FINALSIZE, "FAPO/COL_THR: thr_state(x)!");
      ifdebug(DGP, DD,
	link = Down(x);
	uplink = Up(x);
	while( link != x && uplink != x )
	{
	  Parent(tmp, uplink);
	  debug1(DGP, DD, "parnt: %s", EchoObject(tmp));
	  Child(tmp, link)
	    ;
	  debug1(DGP, DD, "child: %s", EchoObject(tmp));
	  link = NextDown(link);
	  uplink = NextUp(uplink);
	}
	while( uplink != x )
	{ Parent(tmp, uplink);
	  debug1(DGP, DD, "extra parnt: %s", EchoObject(tmp));
	  uplink = NextUp(uplink);
	}
	while( link != x )
	{ Child(tmp, link)
	    ;
	  debug1(DGP, DD, "extra child: %s", EchoObject(tmp));
	  link = NextDown(link);
	}
      )
      i = 1;  res = nilobj;
      while( Down(x) != x && Up(x) != x )
      {
	New(fixed_thr, type(x) == COL_THR ? FIXED_COL_THR : FIXED_ROW_THR);
	MoveLink(Up(x), fixed_thr, CHILD);
	MoveLink(Down(x), fixed_thr, PARENT);
	back(fixed_thr, dim) = xb;
	fwd(fixed_thr, dim) = xf;
	if( count == i )
	  res = fixed_thr;
	i++;
      }
      if( Up(x) != x || Down(x) != x )
      {
	debug2(DGP, DD, "links problem at %s %d:", Image(type(x)), (int) x);
	if( Up(x) != x )
	{
	  Parent(tmp, Up(x));
	  debug1(DGP, DD, "first parent is %s", EchoObject(tmp));
	}
	if( Down(x) != x )
	{
	  Child(tmp, Down(x))
	    ;
	  debug1(DGP, DD, "first child is %s", EchoObject(tmp));
	}
      }
      assert( Up(x) == x && Down(x) == x, "FAPO/COL_THR: x links!" );
      Dispose(x);
      assert(res != nilobj, "FixAndPrintObject: COL_THR res!");
      x = res;
      *** */
      /* NB NO BREAK! */


    /* ***
    case FIXED_COL_THR:
    case FIXED_ROW_THR:

      assert( (type(x) == FIXED_COL_THR) == (dim == COLM),
	"FixAndPrintObject: fixed_thr!" );
      CountChild(y, Down(x), count)
        ;
      y = FixAndPrintObject(y, xmk, back(x, dim), fwd(x, dim), dim,
	NO_SUPPRESS, pg, count, &aback, &afwd);
      *actual_back = back(x, dim);  *actual_fwd = fwd(x, dim);
      break;
      *** */


    case BEGIN_HEADER:
    case END_HEADER:
    case SET_HEADER:
    case CLEAR_HEADER:

      if( dim == COLM )
        Error(23, 8, "%s symbol ignored (out of place)", WARN, &fpos(x),
	  Image(type(x)));
      break;


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


  } /* end switch */
  debug2(DGP, DD, "] FixAndPrintObject returning (actual %s,%s).",
	EchoLength(*actual_back), EchoLength(*actual_fwd));
  return res;
} /* end FixAndPrintObject */
