/*@z22.c:Galley Service:Interpose()@******************************************/
/*                                                                           */
/*  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:         z22.c                                                      */
/*  MODULE:       Galley Service                                             */
/*  EXTERNS:      Interpose(), FlushInners(), ExpandRecursives(),            */
/*                Promote(), KillGalley(), FreeGalley(),                     */
/*                SetTarget(), CheckComponentOrder()                         */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"

/* these three variables refer to the root galley only */
static BOOLEAN first = TRUE;		  /* if first component unwritten    */
static OBJECT page_label = nilobj;	  /* current page label object       */
static OBJECT prev_page_label = nilobj; /* previous page label object      */


/*****************************************************************************/
/*                                                                           */
/*  void PromoteInit(void)                                                   */
/*                                                                           */
/*  Initialize this module.                                                  */
/*                                                                           */
/*****************************************************************************/

void PromoteInit(void)
{
  first = TRUE;
  page_label = nilobj;
  prev_page_label = nilobj;
}


/*****************************************************************************/
/*                                                                           */
/*  Interpose(z, typ, x, y)                                                  */
/*                                                                           */
/*  Insert a new typ object above z.  Its sizes are to be taken from x       */
/*  (column) and y (row).                                                    */
/*                                                                           */
/*****************************************************************************/

void Interpose(OBJECT z, int typ, OBJECT x, OBJECT y)
{ OBJECT encl;
  New(encl, typ);
  FposCopy(fpos(encl), fpos(y));
  ReplaceNode(encl, z);  Link(encl, z);
  back(encl, COLM) = back(x, COLM);
  fwd(encl, COLM) = fwd(x, COLM);
  back(encl, ROWM) = back(y, ROWM);
  fwd(encl, ROWM) = fwd(y, ROWM);
  underline(encl) = underline(z);
} /* end Interpose */


/*@::FlushInners()@***********************************************************/
/*                                                                           */
/*  FlushInners(inners, hd)                                                  */
/*                                                                           */
/*  Flush each galley on the list inners.  These have become flushable       */
/*  by being promoted off the top of galley hd; if hd is the root galley,    */
/*  identifiable by having PrintSym as target, do not flush inners at all.   */
/*                                                                           */
/*****************************************************************************/

void FlushInners(OBJECT inners, OBJECT hd)
{ OBJECT y, z, tmp, dest_index;

  debug1(DGA, D, "[ FlushInners(%s, -)", DebugInnersNames(inners));
  ifdebug(DGF, D,
    OBJECT link;
    fprintf(stderr, "dgf: [ FlushInners(");
    for( link = Down(inners);  link != inners;  link = NextDown(link) )
    {
      Child(y, link);
      fprintf(stderr, " %s", Image(type(y)));
      switch( type(y) )
      {

        case DEAD:
      
	  break;


        case RECEIVING:
        case UNATTACHED:
      
	  if( Down(y) != y )	/* bug fix (was assert before) */
	  { assert( Down(y) != y, "FlushInners: UNATTACHED!");
	    Child(z, Down(y));
	    fprintf(stderr, " %s", SymName(actual(z)));
	  }
	  break;


        case PRECEDES:
      
	  break;


        case GALL_PREC:

	  break;


        default:
      
	  break;
      }
    }
    fprintf(stderr, ")");
    debug0(DGF, D, "");
  )

  /* check for root galley case */
  if( hd != nilobj )
  { assert( Up(hd) != hd, "FlushInners: Up(hd)!" );
    Parent(dest_index, Up(hd));
    if( actual(actual(dest_index)) == PrintSym )
    { DisposeObject(inners);
      debug0(DGA, D, "] FlushInners returning (PrintSym)");
      debug0(DGF, D, "] FlushInners returning (PrintSym)");
      return;
    }
  }

  while( Down(inners) != inners )
  { Child(y, Down(inners));
    DeleteLink(Down(inners));
    debug2(DGA, D, "FlushInners at %s (remainder %s)", Image(type(y)),
      DebugInnersNames(inners));
    switch( type(y) )
    {

      case DEAD:
      
	break;


      case RECEIVING:
      case UNATTACHED:
      
	if( Down(y) != y )	/* bug fix (was assert before) */
	{ assert( Down(y) != y, "FlushInners: UNATTACHED!");
	  Child(z, Down(y));
	  debug1(DGA,D,"  possibly calling FlushGalley %s from FlushInners (a)",
	    SymName(actual(z)));
	  if( whereto(z) != nilobj )
	    debug2(DGA,D,"    (whereto(z) = %s, uses_extern_target = %s)",
	      SymName(whereto(z)), bool(uses_extern_target(whereto(z))));
	  if( whereto(z)==nilobj || !uses_extern_target(whereto(z)) ) /* &&& */
	    FlushGalley(z);
	}
	break;


      case PRECEDES:
      
	Child(tmp, Down(y));
	if( Up(tmp) != LastUp(tmp) )
	{ Parent(tmp, LastUp(tmp));
	  assert(type(tmp)==FOLLOWS, "FlushInners: FOLLOWS!");
	  if( blocked(tmp) )
	  { blocked(tmp) = FALSE;
	    Parent(z, Up(tmp));
	    debug0(DGF, D, "  calling FlushGalley from FlushInners (b)");
	    if( whereto(z)==nilobj || !uses_extern_target(whereto(z)) )/* &&& */
	      FlushGalley(z);
	  }
	}
	break;


      case GALL_PREC:

	/* someone else is looking after this now */
	break;


      default:
      
	assert1(FALSE, "FlushInners:", Image(type(y)));
	break;
    }
  }
  Dispose(inners);
  debug0(DGA, D, "] FlushInners returning");
  debug0(DGF, D, "] FlushInners returning");
} /* end FlushInners */


/*@::ExpandRecursives()@******************************************************/
/*                                                                           */
/*  ExpandRecursives(recs)                                                   */
/*                                                                           */
/*  Expand each of the recursive definite objects in the list recs.          */
/*                                                                           */
/*****************************************************************************/

void ExpandRecursives(OBJECT recs)
{ CONSTRAINT non_c, hc, vc;
  OBJECT target_index, target, z = nilobj, n1, inners, newrecs, hd, tmp, env, why;
  debug0(DCR, DDD, "ExpandRecursives(recs)");
  SetConstraint(non_c, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);
  n1 = nilobj;
  assert(recs != nilobj, "ExpandRecursives: recs == nilobj!");
  while( Down(recs) != recs )
  { Child(target_index, Down(recs));  DeleteLink( Down(recs) );
    assert( type(target_index) == RECURSIVE, "ExpandRecursives: index!" );
    target = actual(target_index);
    debug2(DCR, DDD, "  expanding %s %s", Image(type(target_index)),
      EchoObject(target));

    /* expand body of target, convert to galley, and check size */
    New(hd, HEAD);  actual(hd) = actual(target);  must_expand(hd) = TRUE;
    force_gall(hd) = FALSE;
    ClearHeaders(hd);
    enclose_obj(hd) = limiter(hd) = nilobj;
    opt_components(hd) = opt_constraints(hd) = nilobj;
    gall_dir(hd) = horiz_galley(actual(target));
    whereto(hd) = ready_galls(hd) = nilobj;
    foll_or_prec(hd) = GALL_FOLL;
    sized(hd) = FALSE;
    seen_nojoin(hd) = FALSE;
    tmp =  CopyObject(target, &fpos(target));  env = DetachEnv(tmp);
    Link(hd, tmp);  Link(target_index, hd);
    SizeGalley(hd, env, external_ver(target),
      gall_dir(hd) == ROWM ? threaded(target) : FALSE, FALSE, FALSE,
      &save_style(target), &non_c, nilobj, &n1, &newrecs, &inners, nilobj);
    debug1(DGA, D, "  ExpandRecursives inners: %s", DebugInnersNames(inners));
    debug0(DCR, DDD, "    as galley:");
    ifdebug(DCR, DDD, DebugObject(hd));
    debug1(DGS, DD, "[ ExpandRecursives calling Constrained(%s, COLM)",
      EchoObject(target));
    Constrained(target, &hc, COLM, &why);
    debug2(DGS, DD, "] ExpandRecursives Constrained(%s, COLM) = %s",
      EchoObject(target), EchoConstraint(&hc));
    debug3(DCR, DDD, "    horizontal size: (%s, %s); constraint: %s",
      EchoLength(back(hd, COLM)), EchoLength(fwd(hd, COLM)), EchoConstraint(&hc));
    if( !FitsConstraint(back(hd, COLM), fwd(hd, COLM), hc) )
    { DisposeChild(Up(hd));
      if( inners != nilobj ) DisposeObject(inners);
      if( newrecs != nilobj ) DisposeObject(newrecs);
      DeleteNode(target_index);
      debug0(DCR, DDD, "    rejecting (too wide)");
      continue;
    }
    if( !external_ver(target) )
    { Constrained(target, &vc, ROWM, &why);
      debug2(DSC, DD, "Constrained( %s, ROWM ) = %s",
	EchoObject(target), EchoConstraint(&vc));
      Child(z, LastDown(hd));
      debug3(DCR, DDD, "    vsize: (%s, %s); constraint: %s",
	EchoLength(back(z, ROWM)), EchoLength(fwd(z, ROWM)), EchoConstraint(&vc));
      if( !FitsConstraint(back(z, ROWM), fwd(z, ROWM), vc) )
      {	DisposeChild(Up(hd));
	if( inners != nilobj ) DisposeObject(inners);
	if( newrecs != nilobj ) DisposeObject(newrecs);
	DeleteNode(target_index);
	debug0(DCR, DDD, "    rejecting (too high)");
	continue;
      }
    }

    /* object fits; adjust sizes and promote */
    debug0(DSA, D, "calling AdjustSize from ExpandRecursives (a)");
    AdjustSize(target, back(hd, COLM), fwd(hd, COLM), COLM);
    if( !external_ver(target) )
    { debug0(DSA, D, "calling AdjustSize from ExpandRecursives (b)");
      AdjustSize(target, back(z, ROWM), fwd(z, ROWM), ROWM);
      Interpose(target, VCAT, z, z);
    }
    debug0(DGS, DD, "calling Promote(hd, hd) from ExpandRecursives");
    Promote(hd, hd, target_index, TRUE);  DeleteNode(hd);
    DeleteNode(target_index);
    if( inners != nilobj )
    {
      debug0(DGF, D, "  calling FlushInners from ExpandRecursives");
      FlushInners(inners, nilobj);
    }
    if( newrecs != nilobj )  MergeNode(recs, newrecs);
  } /* end while */
  Dispose(recs);
  debug0(DCR, DDD, "ExpandRecursives returning.");
} /* end ExpandRecursives */

/*@::FindSplitInGalley()@*****************************************************/
/*                                                                           */
/*  static OBJECT FindSplitInGalley(hd)                                      */
/*                                                                           */
/*  Search simply joined galley hd for a SPLIT object, which must be there.  */
/*                                                                           */
/*****************************************************************************/

static OBJECT FindSplitInGalley(OBJECT hd)
{ OBJECT link, y = nilobj;
  debug0(DGF, D, "FindSplitInGalley(hd)");
  for( link = Down(hd);  link != hd;  link = NextDown(link) )
  { Child(y, link);
    if( is_definite(type(y)) )  break;
  }
  if( link == hd )
  { debug0(DGF, D, "FindSplitInGalley failing, no definite component; hd =");
    ifdebug(DGF, D, DebugObject(hd));
    Error(22, 1, "FindSplit: missing galley component", INTERN, &fpos(hd));
  }
  while( type(y) != SPLIT )  switch( type(y) )
  {
    case VCAT:
    case ONE_ROW:
    case WIDE:
    case HIGH:
    case HSHIFT:
    case VSHIFT:
    case VCONTRACT:
    case VLIMITED:
    case VEXPAND:

      Child(y, Down(y));
      break;


    case BEGIN_HEADER:
    case SET_HEADER:

      Child(y, LastDown(y));
      break;


    case CLOSURE:
    case NULL_CLOS:
    case END_HEADER:
    case CLEAR_HEADER:
    case PAGE_LABEL:
    case HCAT:
    case WORD:
    case QWORD:
    case ACAT:
    case ROW_THR:
    case COL_THR:
    case ONE_COL:
    case SCALE:
    case KERN_SHRINK:
    case HMIRROR:
    case VMIRROR:
    case HSCALE:
    case VSCALE:
    case HCOVER:
    case VCOVER:
    case HCONTRACT:
    case HLIMITED:
    case HEXPAND:
    case START_HVSPAN:
    case START_HSPAN:
    case START_VSPAN:
    case HSPAN:
    case VSPAN:
    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:

      debug0(DGF, D, "FindSplitInGalley(hd) failing, hd =");
      ifdebug(DGF, D, DebugObject(hd));
      Error(22, 2, "FindSplitInGalley failed", INTERN, &fpos(y),Image(type(y)));
      break;


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

  }
  debug0(DGF, D, "FindSplitInGalley returning.");
  return y;
} /* end FindSplitInGalley */


/*****************************************************************************/
/*                                                                           */
/*  ClearHeaders(OBJECT hd)                                                  */
/*                                                                           */
/*  Clear the headers of hd.                                                 */
/*                                                                           */
/*****************************************************************************/

void ClearHeaders(OBJECT hd)
{ int i;
  for( i = 0;  i < MAX_HCOPIES;  i++ )
    headers(hd, i) = nilobj;
  head_next(hd) = 0;
} /* end ClearHeaders */


/*****************************************************************************/
/*                                                                           */
/*  DisposeHeaders(OBJECT hd)                                                */
/*                                                                           */
/*  Dispose the headers of hd.                                               */
/*                                                                           */
/*****************************************************************************/

static void DisposeHeaders(OBJECT hd)
{ int i;
  for( i = 0;  i < MAX_HCOPIES;  i++ )
  {
    if( headers(hd, i) != nilobj )
    { assert(type(headers(hd, i)) == ACAT || type(headers(hd, i)) == VCAT,
        "DisposeHeaders: type(headers(hd))!");
      while( Down(headers(hd, i)) != headers(hd, i) )
      { DisposeChild(Down(headers(hd, i)));
      }
      headers(hd, i) = nilobj;
    }
  }
} /* end DisposeHeaders */


/*@::HandleHeader()@**********************************************************/
/*                                                                           */
/*  HandleHeader(hd, link, header)                                           */
/*                                                                           */
/*  Given galley hd containing header object header, this routine handles    */
/*  the header, i.e. it updates headers(hd) to reflect the effect of the     */
/*  header, then deletes the header and its following gap object.            */
/*                                                                           */
/*  Link is the link from hd to the header (it may actually link to a        */
/*  split object which then links to the header, but no matter).             */
/*                                                                           */
/*  Actually, we no longer allow a SPLIT object above a header, because      */
/*  that means there is a COL_THR or ROW_THR above it and when we dispose    */
/*  the header, Dispose is not able to dispose the appropriate child of the  */
/*  COL_THR or ROW_THR.  So Manifest() must not insert a SPLIT above a       */
/*  header, and we check for that.                                           */
/*                                                                           */
/*****************************************************************************/

void HandleHeader(OBJECT hd, OBJECT header)
{ OBJECT g, gaplink, gap_obj;
  int i;
  debug3(DGS, D, "[ HandleHeader(%s, %s); headers = %s; galley:",
    SymName(actual(hd)), EchoObject(header), EchoObject(headers(hd, head_next(hd))));
  ifdebug(DGS, DD, DebugGalley(hd, nilobj, 2));
  assert(is_header(type(header)), "HandleHeader: type(header)!");
  assert(Up(header) == LastUp(header) && Up(header) != header,
    "HandleHeader: header parents!");
  switch( type(header) )
  {

    case CLEAR_HEADER:

      /* clear out old headers, if any */
      DisposeHeaders(hd);
      break;


    case SET_HEADER:

      /* clear out old headers (not safe to dispose them yet), if any */
      DisposeHeaders(hd);
      /* NB NO BREAK! */


    case BEGIN_HEADER:

      Child(gap_obj, Down(header));
      for( i = 0;  i < MAX_HCOPIES;  i++ )
      {
        /* make new headers if required */
        if( headers(hd, i) == nilobj )
	  New(headers(hd, i), gall_dir(hd) == ROWM ? VCAT : ACAT);

        /* construct a gap object from the left parameter */
        New(g, GAP_OBJ);
        underline(g) = FALSE;
	Link(g, CopyObject(gap_obj, &fpos(gap_obj)));
        GapCopy(gap(g), line_gap(save_style(header)));
        mark(gap(g)) = join(gap(g)) = FALSE;

        /* move header and gap into headers() */
        MoveLink(NextDown(Down(header)), headers(hd, i), PARENT);
        Link(headers(hd, i), g);
        debug2(DGS, D, "HandleHeader moving %s header %s",
	  SymName(actual(hd)), Image(type(header)));
      }
      break;


    case END_HEADER:

      for( i = 0;  i < MAX_HCOPIES;  i++ )
      {
        if( headers(hd, i) == nilobj )
	  Error(22, 11, "no header for %s to remove", WARN, &fpos(hd),
	    KW_END_HEADER);
        else
        {
	  /* dispose last gap */
	  assert(LastDown(headers(hd, i))!=headers(hd, i), "Promote/END_HEADER!");
	  Child(g, LastDown(headers(hd, i)));
	  assert(type(g) == GAP_OBJ, "HandleHeader: END_HEADER/gap!");
	  DisposeChild(LastDown(headers(hd, i)));

	  /* dispose last header object */
	  assert(LastDown(headers(hd, i))!=headers(hd, i), "Promote/END_HEADER!");
	  DisposeChild(LastDown(headers(hd, i)));
	  if( Down(headers(hd, i)) == headers(hd, i) )
	  { DisposeObject(headers(hd, i));
	    headers(hd, i) = nilobj;
	  }
        }
      }
      break;
	
  }

  /* dispose header object (must take care to disentangle safely) */
  gaplink = NextDown(Up(header));
  assert(type(gaplink) == LINK, "HandleHeader: type(gaplink)!");
  if( type(header) == CLEAR_HEADER || type(header) == END_HEADER )
  {
    /* first disentangle child properly */
    assert(Down(header) != header && Down(header) == LastDown(header), "HH!");
    DisposeChild(Down(header));
  }
  DisposeChild(Up(header));
  DisposeChild(gaplink);

  debug2(DGS, D, "] HandleHeader returning; headers(%s) now %s; galley:",
    SymName(actual(hd)), EchoObject(headers(hd, 0)));
  ifdebug(DGS, DD, DebugGalley(hd, nilobj, 2));
} /* end HandleHeader */


/*@::Promote()@***************************************************************/
/*                                                                           */
/*  Promote(hd, stop_link, dest_index, join_after)                           */
/*                                                                           */
/*  Promote components of galley hd into its destination (dest), up to but   */
/*  not including the one linked to hd by link stop_link, which always       */
/*  follows a component.  No size adjustments are made, except that when     */
/*  two col_thr nodes are merged, a COLM adjustment is made to the result.   */
/*                                                                           */
/*  If the galley is ending here, Promote inserts a gap at the end of it.    */
/*  Whether to make this a joining gap or not is a tricky question which     */
/*  Promote answers by referring to join_after.                              */
/*                                                                           */
/*  Any BEGIN_HEADER, END_HEADER, SET_HEADER, or CLEAR_HEADER objects in     */
/*  the promoted components are diverted to headers(hd), if the target       */
/*  is internal.                                                             */
/*                                                                           */
/*****************************************************************************/

void Promote(OBJECT hd, OBJECT stop_link, OBJECT dest_index, BOOLEAN join_after)
{
  OBJECT dest, link, y, z, tmp1, tmp2, why, top_y;
  FULL_CHAR *label_string, buff[MAX_LINE];
  FULL_LENGTH aback, afwd;
  int dim, pnval;
  debug2(DGS, DD, "[ Promote(%s width %s, stop_link):",
    SymName(actual(hd)), EchoLength(size(hd, COLM)));
  ifdebug(DGS, DD, DebugGalley(hd, stop_link, 2));

  assert( type(hd) == HEAD, "Promote: hd!" );
  assert( type(stop_link) == LINK || stop_link == hd, "Promote: stop_link!" );
  assert( stop_link != Down(hd), "Promote: stop_link == Down(hd)!" );
  type(dest_index) = RECEIVING;
  dest = actual(dest_index);

  /* insert final gap if galley is ending */
  if( stop_link != hd )
  { Child(y, stop_link);
    if( type(y) != GAP_OBJ )
    { ifdebug(DGS, DD, DebugGalley(hd, stop_link, 2));
    }
    assert( type(y) == GAP_OBJ, "Promote: missing GAP_OBJ!" );
    stop_link = NextDown(stop_link);
  }
  else
  { New(y, GAP_OBJ);
    FposCopy(fpos(y), fpos(hd));
    hspace(y) = 0;  vspace(y) = 1;
    /* SetGap(gap(y), FALSE, FALSE, seen_nojoin(hd), FIXED_UNIT, NO_MODE, 0); */
    /* SetGap(gap(y), FALSE, FALSE, threaded(dest), FIXED_UNIT, NO_MODE, 0); */
    /* SetGap(gap(y), FALSE, FALSE, TRUE, FIXED_UNIT, NO_MODE, 0); */
    /* ClearGap(gap(y)); */
    SetGap(gap(y), FALSE, FALSE, join_after, FIXED_UNIT, NO_MODE, 0);
    Link(stop_link, y);
  }

  /* if optimizing, add to dummy paragraph containing components and gaps */
  if( opt_components(hd) != nilobj )
  { OBJECT last, tmp;

    debug1(DOG, DD, "Promote(%s) optimizing:", SymName(actual(hd)));
    if( LastDown(opt_components(hd))!=opt_components(hd) && !opt_gazumped(hd) )
    {
      Child(last, LastDown(opt_components(hd)));
    }
    else last = nilobj;
    for( link = Down(hd);  link != stop_link;  link = NextDown(link) )
    { Child(y, link);
      if( type(y) == GAP_OBJ )
      {
	if( last == nilobj )
	{
	  /* do nothing, gap cannot separate definite objects */
	  debug1(DOG, DD, "  skipping initial GAP_OBJ %s", EchoGap(&gap(y)));
	}
	else if( type(last) == GAP_OBJ )
	{
	  /* previous gap must have preceded an indefinite, so overwrite it */
	  FposCopy(fpos(last), fpos(y));
	  debug2(DOG, DD, "  overwriting GAP_OBJ %s with %s",
	    EchoGap(&gap(last)), EchoGap(&gap(y)));
	  GapCopy(gap(last), gap(y));
	  if( Down(last) != last )  DisposeChild(Down(last));
	  if( Down(y) != y )
	  { Child(tmp, Down(y));
	    tmp = CopyObject(tmp, no_fpos);
	    Link(last, tmp);
	  }
	  join(gap(last)) = TRUE;  /* irrelevant but improves debug output */
	}
	else
	{
	  /* previous was definite, so this gap must be stored */
	  opt_gazumped(hd) = FALSE;
	  New(last, GAP_OBJ);
	  FposCopy(fpos(last), fpos(y));
	  GapCopy(gap(last), gap(y));
	  join(gap(last)) = TRUE;  /* irrelevant but improves debug output */
	  hspace(last) = 1;
	  vspace(last) = 0;
	  Link(opt_components(hd), last);
	  debug1(DOG, DD, "  adding GAP_OBJ %s", EchoGap(&gap(last)));
	}
      }
      else if( is_word(type(y)) )
      {
	/* definite, must be stored */
	opt_gazumped(hd) = FALSE;
	last = MakeWord(type(y), string(y), &fpos(y));
	back(last, COLM) = back(y, gall_dir(hd));
	fwd(last, COLM) = fwd(y, gall_dir(hd));
	word_font(last) = word_font(y);
	word_colour(last) = word_colour(y);
	word_underline_colour(last) = word_underline_colour(y);
	word_texture(last) = word_texture(y);
	word_outline(last) = word_outline(y);
	word_language(last) = word_language(y);
	word_baselinemark(last) = word_baselinemark(y);
	word_strut(last) = word_strut(y);
	word_ligatures(last) = word_ligatures(y);
	word_hyph(last) = word_hyph(y);
	Link(opt_components(hd), last);
	debug2(DOG, DD, "  adding %s \"%s\"", Image(type(last)), string(last));
      }
      else if( is_indefinite(type(y)) )
      {
	/* indefinite, always skip these */
      }
      else if( is_definite(type(y)) )
      {
	/* definite other than WORD, add it */
	opt_gazumped(hd) = FALSE;
	last = MakeWord(QWORD, AsciiToFull("w"), &fpos(y));
	back(last, COLM) = back(y, gall_dir(hd));
	fwd(last, COLM) = fwd(y, gall_dir(hd));
	Link(opt_components(hd), last);
	debug1(DOG, DD, "  adding word for %s", EchoObject(y));
      }
    }
    debug1(DOG, DD, "Promote(%s) end optimizing", SymName(actual(hd)));
  }

  /* error if promoting a seen_nojoin galley into a threaded destination */
  if( seen_nojoin(hd) && gall_dir(hd) == ROWM && threaded(dest) )
    Error(22, 3, "galley %s must have a single column mark",
      FATAL, &fpos(hd), SymName(actual(hd)));

  /* make nojoin status clear by adding an extra gap at start if needed */
  if( gall_dir(hd) == ROWM && !external_ver(dest) && seen_nojoin(hd) &&
      join(gap(y)) )
  { OBJECT prnt, extra_null, extra_gap;

    /* add nojoin gap at start */
    Parent(prnt, Up(dest));  /* can't be threaded */
    assert( type(prnt) == VCAT, "Promote: nojoin case, can't find VCAT" );
    New(extra_null, NULL_CLOS);
    back(extra_null, COLM) = fwd(extra_null, COLM) = 0;
    back(extra_null, ROWM) = fwd(extra_null, ROWM) = 0;
    New(extra_gap, GAP_OBJ);
    hspace(extra_gap) = vspace(extra_gap) = 0;
    SetGap(gap(extra_gap), FALSE, FALSE, FALSE, FIXED_UNIT, EDGE_MODE, 0);
    Link(Down(prnt), extra_gap);
    Link(Down(prnt), extra_null);
    debug0(DGS, DD, "  Promote adding extra nojoin gap");
    /* join(gap(y)) = FALSE; */
  }


  /* if promoting out of root galley, do special things */
  if( actual(dest) == PrintSym )
  { CONSTRAINT c;
    link = hd;
    while( NextDown(link) != stop_link )
    { Child(y, NextDown(link));
      debug2(DGS, DD, "root promote %s %s", Image(type(y)),
	is_definite(type(y)) ? STR_EMPTY : EchoObject(y));
      if( type(y) == SPLIT )  Child(y, DownDim(y, ROWM));
      switch( type(y) )
      {

	case SCALE_IND:
	case COVER_IND:
	case PRECEDES:
      
	  DisposeChild(NextDown(link));
	  break;
	

	case UNATTACHED:
      
	  assert( Down(y) != y, "FlushRootGalley: UNATTACHED!" );
	  Child(z, Down(y));
	  assert( type(z) == HEAD, "FlushRootGalley: unattached HEAD!" );
	  if( sized(z) )
	  {
	    /* galley is part flushed, leave it here */
	    link = NextDown(link);
	  }
	  /* ??? else if( foll_or_prec(z) == GALL_PREC ) */
	  else if( foll_or_prec(z) == GALL_PREC ||
		   foll_or_prec(z) == GALL_FOLL_OR_PREC )
	  {
	    /* galley is preceding or foll_or_prec, send to CrossSequence */
	    OBJECT t;
	    /* type(y) = GALL_PREC; */
	    type(y) = foll_or_prec(z);
	    pinpoint(y) = nilobj;
	    Child(t, Down(z));
	    /* actual(y) = CrossMake(whereto(z), t, GALL_PREC); */
	    actual(y) = CrossMake(whereto(z), t, type(y));
	    DisposeChild(Down(y));
	    CrossSequence(actual(y));
	    DisposeChild(NextDown(link));
	  }
	  else
	  {
	    /* galley was never attached, print message and kill it */
	    Error(22, 4, "galley %s deleted (never attached)",
	      WARN, &fpos(z), SymName(actual(z)));
	    debug1(DGF, D, "never-attached galley %s:", EchoFilePos(&fpos(z)));
	    ifdebug(DGF, D, DebugObject(z));
	    KillGalley(z, FALSE);
	    /* ***
	    link = NextDown(link);
	    *** */
	  }
	  break;


	case EXPAND_IND:
      
	  /* expand @HExpand or @VExpand to occupy everything possible */
	  dim = type(actual(y)) == HEXPAND ? COLM : ROWM;
	  Constrained(actual(y), &c, dim, &why);
	  if( constrained(c) )
	  { FULL_LENGTH b = back(actual(y), dim);
	    FULL_LENGTH f = fwd(actual(y), dim);
	    EnlargeToConstraint(&b, &f, &c);
	    debug1(DSA, D, "Promote %s AdjustSize", Image(type(actual(y))));
	    AdjustSize(actual(y), b, f, dim);
	  }
	  DisposeChild(NextDown(link));
	  break;


	case PAGE_LABEL_IND:

	  if( page_label != nilobj )
	  { DisposeObject(page_label);
	    page_label = nilobj;
	  }
	  Child(z, Down(y));
	  assert( type(z) == PAGE_LABEL, "Promote: type(z) != PAGE_LABEL!" );
	  assert( Down(z) != z, "Promote: PAGE_LABEL Down(z) == z!" );
	  Child(page_label, Down(z));
	  DeleteLink(Up(page_label));
	  DisposeChild(NextDown(link));
	  break;


	case CROSS_PREC:
	case CROSS_FOLL:
	case CROSS_FOLL_OR_PREC:
	case CROSS_TARG:
	      
	  debug2(DGS, DD, "root promote %s %s", Image(type(y)), EchoObject(y));
	  /* NB NO BREAK */


	case GALL_PREC:
	case GALL_FOLL:
	case GALL_FOLL_OR_PREC:
	case GALL_TARG:

	  CrossSequence(actual(y));
	  DisposeChild(NextDown(link));
	  break;


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

	  Error(22, 10, "%s symbol ignored (out of place)", WARN, &fpos(y),
	    Image(type(y)));
	  DisposeChild(NextDown(link));
	  break;


	case WORD:
	case QWORD:
	case ONE_COL:
	case ONE_ROW:
	case WIDE:
	case HIGH:
	case HSHIFT:
	case VSHIFT:
	case HMIRROR:
	case VMIRROR:
	case HSCALE:
	case VSCALE:
	case HCOVER:
	case VCOVER:
	case HCONTRACT:
	case VCONTRACT:
	case HLIMITED:
	case VLIMITED:
	case HEXPAND:
	case VEXPAND:
	case START_HVSPAN:
	case START_HSPAN:
	case START_VSPAN:
	case HSPAN:
	case VSPAN:
	case ROTATE:
	case BACKGROUND:
	case SCALE:
	case KERN_SHRINK:
	case INCGRAPHIC:
	case SINCGRAPHIC:
	case PLAIN_GRAPHIC:
	case GRAPHIC:
	case LINK_SOURCE:
	case LINK_DEST:
	case LINK_URL:
	case ACAT:
	case HCAT:
	case ROW_THR:

	case CLOSURE:
	case NULL_CLOS:
	case LINK_DEST_NULL:
	case PAGE_LABEL:
	case CROSS:
	case FORCE_CROSS:

	  /* print this component */
	  debug0(DGS, DD, "root promote definite or indefinite");
	  if( !is_indefinite(type(y)) && size(y, ROWM) != 0 )
	  {
	    /* fix horizontally; work out which fonts needed */
	    SetLengthDim(COLM);
	    debug3(DGP,D, "  Promote calling FixAndPrint %s %s,%s", dimen(COLM),
	      EchoLength(back(y,COLM)), EchoLength(fwd(y, COLM)));
	    FixAndPrintObject(y, back(y, COLM), back(y, COLM), fwd(y, COLM),
	      COLM, FALSE, 0, 0, &aback, &afwd);

	    /* get label string from page_label, or prev_page_label + 1 */
	    if( page_label != nilobj && is_word(type(page_label)) )
	      label_string = string(page_label);
	    else if( prev_page_label!=nilobj && is_word(type(prev_page_label))
		&& sscanf( (char *) string(prev_page_label), "%d", &pnval)==1 )
	    {
	      sprintf( (char *) buff, "%d", pnval + 1);
	      label_string = buff;
	    }
	    else
	      label_string = AsciiToFull("?");

	    /* print prefatory or page separating material, including fonts */
	    debug1(DGS, DD, "root promote definite; label_string = %s",
	      label_string);
	    debug1(DCR, DD, "label_string = %s", label_string);
	    if( first )
	    { BackEnd->PrintBeforeFirstPage(size(hd, COLM), size(y, ROWM),
		label_string);
	      first = FALSE;
	    }
	    else
	      BackEnd->PrintBetweenPages(size(hd, COLM), size(y, ROWM),
		label_string);

	    /* dispose prev_page_label and move page_label to prev_page_label */
	    if( prev_page_label != nilobj )
	      DisposeObject(prev_page_label);
	    prev_page_label = page_label;
	    page_label = nilobj;

	    /* fix and print vertically */
	    debug1(DGF,D, "  Promote calling FixAndPrint %s", Image(type(y)));
	    debug3(DGP,D, "  Promote calling FixAndPrint %s %s,%s", dimen(ROWM),
	      EchoLength(back(y,ROWM)), EchoLength(fwd(y, ROWM)));
	    SetLengthDim(ROWM);
	    FixAndPrintObject(y, back(y,ROWM), back(y, ROWM), fwd(y, ROWM),
	      ROWM, FALSE, size(y, ROWM), 0, &aback, &afwd);

	  }
	  DisposeChild(NextDown(link));

	  /* scavenge any filter files now not needed */
	  FilterScavenge(FALSE);
	  break;


	case GAP_OBJ:

	  DisposeChild(NextDown(link));
	  break;


	default:
      
	  assert1(FALSE, "Promote:", Image(type(y)));
	  break;
	
      }
    }
    debug0(DGS, DD, "Promote returning (root galley).");
    return;
  }

  /* prepare the promotion */
  if( external_ver(dest) && gall_dir(hd) == ROWM )
  { if( threaded(dest) )
    { Parent(tmp1, UpDim(dest, COLM));
      assert( type(tmp1) == COL_THR, "Promote: tmp1 not COL_THR!" );
      y = FindSplitInGalley(hd);
      assert( type(y) == SPLIT, "Promote: FindSplitInGalley!" );
      Child(tmp2, DownDim(y, COLM));
      assert( type(tmp2) == COL_THR, "Promote: tmp2 not COL_THR!" );
      if( tmp1 != tmp2 )
      { FULL_LENGTH b = find_max(back(tmp1, COLM), back(tmp2, COLM));
	FULL_LENGTH f = find_max(fwd(tmp1, COLM),  fwd(tmp2, COLM));
	debug0(DSA, D, "calling AdjustSize(tmp1) from Promote (node merging)");
	AdjustSize(tmp1, b, f, COLM);
	debug0(DSA, D, "calling AdjustSize(tmp2) from Promote (node merging)");
	AdjustSize(tmp2, b, f, COLM);
	MergeNode(tmp1, tmp2);
      }
    }
    link = Up(dest_index);
  }
  else if( external_hor(dest) && gall_dir(hd) == COLM )
  { link = Up(dest_index);
  }
  else
  {
    dim = gall_dir(hd);
    for( link = hd;  NextDown(link) != stop_link;  )
    { Child(y, NextDown(link));
      debug1(DGS, DD, "ordinary promote examining %s", EchoObject(y));
      top_y = y;
      if( type(y) == SPLIT )
	Child(y, DownDim(y, dim));
      if( is_header(type(y)) )
      {
	assert(top_y == y, "Promote: header under SPLIT!");
	HandleHeader(hd, y);
      }
      else if( is_index(type(y)) )
	MoveLink(NextDown(link), Up(dest_index), PARENT);
      else link = NextDown(link);
    }
    assert( Down(hd) != stop_link, "Promote: Down(hd) == stop_link!" );
    assert( UpDim(dest, ROWM) == UpDim(dest, COLM), "Promote: dims!" );
    link = Up(dest);
  }
  
  /* promote components */
  TransferLinks(Down(hd), stop_link, link);

  debug0(DGS, DD, "] Promote returning; galley:");
  ifdebug(DGS, DD, DebugGalley(hd, nilobj, 2));
} /* end Promote */


/*@::MakeDead(), KillGalley()@************************************************/
/*                                                                           */
/*  static MakeDead(y)                                                       */
/*                                                                           */
/*  Convert object y into a DEAD object and remove it to the dead store.     */
/*                                                                           */
/*****************************************************************************/

static void MakeDead(OBJECT y)
{ static int	dead_count = 0;		/* number of DEAD objects seen       */
  static OBJECT	dead_store = nilobj;	/* where DEAD objects are kept       */

  debug1(DGS, DDD, "MakeDead( %s )", Image(type(y)));
  if( dead_store == nilobj )  New(dead_store, ACAT);
  type(y) = DEAD;
  MoveLink(Up(y), dead_store, PARENT);
  if( dead_count >= 150 )
  { DisposeChild(Down(dead_store));
  }
  else dead_count++;
  debug1(DGS, DDD, "MakeDead returning (dead_count = %d).", dead_count);
} /* end MakeDead */


/*****************************************************************************/
/*                                                                           */
/*  KillGalley(hd, optimize)                                                 */
/*                                                                           */
/*  Kill galley hd, which may be sized or unsized.  The index of hd must     */
/*  be UNATTACHED; it is moved out of its present location to a secret spot. */
/*                                                                           */
/*  If hd is to be optimized, generate all the stuff for the cross           */
/*  reference database.  However, don't do this if optimize is FALSE, for    */
/*  in that case hd is defective in some way and not optimizable.            */
/*                                                                           */
/*****************************************************************************/

void KillGalley(OBJECT hd, BOOLEAN optimize)
{ OBJECT prnt, link, y, z;
  debug2(DGF, D, "[ KillGalley(Galley %s into %s)",
	SymName(actual(hd)), SymName(whereto(hd)));
  assert( type(hd) == HEAD && Up(hd) != hd, "KillGalley: precondition!" );
  Parent(prnt, Up(hd));
  assert( type(prnt) == UNATTACHED, "KillGalley: UNATTACHED precondition!" );
  assert( Up(prnt) != prnt, "KillGalley: prnt!" );

  /* delete any ready_galls that might be hanging about */
  if( ready_galls(hd) != nilobj )
  { DisposeObject(ready_galls(hd));
    ready_galls(hd) = nilobj;
  }

  /* delete every remaining component */
  for( link = hd; NextDown(link) != hd; )
  { Child(y, NextDown(link));
    switch( type(y) )
    {
      case RECEIVING:	while( Down(y) != y )
			{ Child(z, Down(y));
			  DetachGalley(z);
			}
			DeleteNode(y);
			break;
		
      case RECEPTIVE:	assert( Down(y) == y, "KillGalley: RECEPTIVE!" );
			DeleteNode(y);
			break;

      case UNATTACHED:	assert( Down(y) != y, "KillGalley: UNATTACHED!" );
			Child(z, Down(y));  KillGalley(z, FALSE);
			break;

      case HEAD:	assert(FALSE, "KillGalley: head");
			break;

      default:		DisposeChild(NextDown(link));
			break;
    }
  }

  /* perform optimization calculations if required */
  if( optimize && opt_components(hd) != nilobj )
    CalculateOptimize(hd);

  /* move index into dead_store */
  MakeDead(prnt);
  debug0(DGF, D, "] KillGalley returning.");
} /* end KillGalley */


/*@::FreeGalley()@************************************************************/
/*                                                                           */
/*  FreeGalley(hd, stop_link, inners, relocate_link, sym)                    */
/*                                                                           */
/*  Free galley hd up to but not including stop_link.  *Inners is well-      */
/*  defined, either nilobj or an ACAT of galleys to be flushed.              */
/*                                                                           */
/*  Relocate_link defines what to do with any galley attached to one of the  */
/*  freed targets.  If it is non-nilobj, galley hd is searched onwards from  */
/*  it to see if a target can be found there.  If so, the galley is          */
/*  relocated to just before that point.  If not, or if relocate_link is     */
/*  nilobj, the galley is freed and added to *inners for flushing.  If the   */
/*  whereto() of such galley is sym, it is freed, not relocated, because the */
/*  cause of this call to FreeGalley is also targeted to sym, and it will    */
/*  consume all possible targets of sym.                                     */
/*                                                                           */
/*****************************************************************************/

void FreeGalley(OBJECT hd, OBJECT stop_link, OBJECT *inners,
OBJECT relocate_link, OBJECT sym)
{ OBJECT link, y, z, zlink, srch, index;
  assert( type(hd) == HEAD && sized(hd), "FreeGalley: pre!");
  assert( Up(hd) != hd, "FreeGalley: Up(hd)!" );
  assert( *inners == nilobj || type(*inners) == ACAT, "FreeGalley: ACAT!" );
  debug3(DGA, D, "[ FreeGalley(Galley %s into %s); rl %s nilobj",
    SymName(actual(hd)), SymName(whereto(hd)),
    relocate_link == nilobj ? "==" : "!=");

  /* close targets and move or flush any inner galleys */
  for( link = Down(hd);  link != stop_link;  link = NextDown(link) )
  { Child(y, link);
    if( type(y) == RECEIVING && actual(actual(y)) == InputSym )
      Error(22, 5, "forcing galley after input point", WARN, &fpos(actual(y)));
    else if( type(y) == RECEIVING )
    {
      /* either relocate or free each galley */
      for( zlink = Down(y);  zlink != y; )
      {	Child(z, zlink);
	zlink = NextDown(zlink);
	assert( type(z) == HEAD, "FreeGalley/RECEIVING: type(z) != HEAD!" );
	debug1(DGA, D, "FreeGalley examining galley %s", SymName(actual(z)));
	if( relocate_link != nilobj && whereto(z) != sym &&
	    (srch = SearchGalley(relocate_link, whereto(z), TRUE,
	    FALSE, TRUE, FALSE)) != nilobj )
	{
	  if( opt_components(z) != nilobj )  GazumpOptimize(z, actual(y));
	  debug2(DGA, D, "  FreeGalley relocating %s to just before %s",
	    SymName(actual(z)), SymName(whereto(z)));
	  DetachGalley(z);
	  Parent(index, Up(z));
	  MoveLink(Up(index), Up(srch), PARENT);  /* just before new dest */
	}
	else
	{ debug1(DGA, D, "  FreeGalley freeing galley %s", SymName(actual(z)));
	  FreeGalley(z, z, inners, nilobj, sym);
	  if( *inners == nilobj )  New(*inners, ACAT);
	  Link(*inners, y);
	}
      }
      non_blocking(y) = TRUE;
    }
    else if( type(y) == RECEPTIVE )
    { non_blocking(y) = TRUE;
    }
  }
  debug0(DGA, D, "] FreeGalley returning.");
} /* end FreeGalley */


/*@::SetTarget()@*************************************************************/
/*                                                                           */
/*  SetTarget(hd)                                                            */
/*                                                                           */
/*  Search for the target of unsized galley hd, and set the following:       */
/*                                                                           */
/*     whereto(hd)          The symbol which is this galley's target         */
/*     foll_or_prec(hd)     GALL_FOLL, GALL_PREC, GALL_FOLL_OR_PREC          */
/*     force_gall(hd)       TRUE is this is a forcing galley                 */
/*                                                                           */
/*****************************************************************************/

void SetTarget(OBJECT hd)
{ OBJECT x, y, link, cr, lpar, rpar, env;
  BOOLEAN copied;
  debug1(DGS, DD, "SetTarget(%s)", SymName(actual(hd)));
  assert( type(hd) == HEAD, "SetTarget: type(hd) != HEAD!" );
  Child(x, Down(hd));
  assert( type(x) == CLOSURE, "SetTarget: type(x) != CLOSURE!" );
  assert( has_target(actual(x)), "SetTarget: x has no target!" );

  /* search the parameters of x for @Target */
  cr = nilobj;
  for( link = Down(x);  link != x;  link = NextDown(link) )
  { Child(y, link);
    if( type(y) == PAR && is_target(actual(y)) )
    { assert( Down(y) != y, "SetTarget: Down(PAR)!" );
      Child(cr, Down(y));
      break;
    }
  }

  /* search the children of actual(x) for a default value of @Target */
  if( cr == nilobj )
  for( link = Down(actual(x));  link != actual(x);  link = NextDown(link) )
  { Child(y, link);
    if( is_target(y) )
    { cr = sym_body(y);
      break;
    }
  }
  assert(cr != nilobj, "SetTarget:  cr == nilobj!");
  
  /* if cr is not a cross-reference, expand it until it is */
  copied = FALSE;
  if( !is_cross(type(cr)) )
  { OBJECT nbt[2], nft[2], ntarget, ncrs, nenclose;
    nbt[COLM] = nft[COLM] = nbt[ROWM] = nft[ROWM] = nilobj;
    ntarget = ncrs = nenclose = nilobj;
    cr = CopyObject(cr, &fpos(x));
    copied = TRUE;
    env = GetEnv(x);
    cr = Manifest(cr, env, &InitialStyle, nbt, nft, &ntarget, &ncrs,
           FALSE, FALSE, &nenclose, TRUE);
  }

  /* check that cr is now a cross-reference object */
  debug1(DGS, DD, "SetTarget examining %s", EchoObject(cr));
  debug1(DGS, DD, "  type(cr) = %s", Image( (int) type(cr)) );
  if( !is_cross(type(cr)) )
    Error(22, 6, "target of %s is not a cross reference",
      FATAL, &fpos(cr), SymName(actual(x)));

  /* determine which symbol is the target of this galley */
  Child(lpar, Down(cr));
  if( type(lpar) != CLOSURE )
    Error(22, 7, "left parameter of %s is not a symbol",
      FATAL, &fpos(lpar), KW_CROSS);
  whereto(hd) = actual(lpar);

  /* determine the direction from the right parameter */
  Child(rpar, NextDown(Down(cr)));
  if( !is_word(type(rpar)) )
  {
    Error(22, 8, "replacing %s%s? by %s%s%s", WARN, &fpos(rpar),
      SymName(actual(lpar)), KW_CROSS,
      SymName(actual(lpar)), KW_CROSS, KW_FOLLOWING);
    foll_or_prec(hd) = GALL_FOLL;
  }
  else if( StringEqual(string(rpar), KW_PRECEDING) )
     foll_or_prec(hd) = GALL_PREC;
  else if( StringEqual(string(rpar), KW_FOLLOWING) )
     foll_or_prec(hd) = GALL_FOLL;
  else if( StringEqual(string(rpar), KW_FOLL_OR_PREC) )
     foll_or_prec(hd) = GALL_FOLL_OR_PREC;
  else
  {
    Error(22, 9, "replacing %s%s%s by %s%s%s",
      WARN, &fpos(rpar), SymName(actual(lpar)), KW_CROSS,
      string(rpar), SymName(actual(lpar)), KW_CROSS, KW_FOLLOWING);
    foll_or_prec(hd) = GALL_FOLL;
  }

  /* determine whether this is a forcing galley */
  force_gall(hd) = force_target(actual(hd)) || type(cr) == FORCE_CROSS;

  if( copied )  DisposeObject(cr);
} /* end SetTarget */


/*@::CheckComponentOrder()@***************************************************/
/*                                                                           */
/*  int CheckComponentOrder(preceder, follower)                              */
/*                                                                           */
/*  Check the ordering relation between components preceder and follower,    */
/*  and return its current status:                                           */
/*                                                                           */
/*      CLEAR     follower definitely follows preceder, and always will;     */
/*      PROMOTE   follower is not prevented from following preceder;         */
/*      CLOSE     follower must move down its galley to follow preceder;     */
/*      BLOCK     follower cannot be guaranteed to follow preceder.          */
/*                                                                           */
/*****************************************************************************/

int CheckComponentOrder(OBJECT preceder, OBJECT follower)
{ OBJECT prec_galley, foll_galley, z;  int res;
  debug2(DGS, DD, "CheckComponentOrder( %s, %s )",
    EchoObject(preceder), EchoObject(follower));
  Parent(prec_galley, Up(preceder));
  Parent(foll_galley, Up(follower));
  if( prec_galley == foll_galley )
  { res = CLOSE;
    for( z = Up(follower);  z != foll_galley;  z = pred(z, CHILD) )
    if( z == Up(preceder) )
    { res = CLEAR;
      break;
    }
  }
  else
  { res = PROMOTE;
    while( Up(prec_galley) != prec_galley )
    { Parent(z, Up(prec_galley));	/* index of galley */
      Parent(prec_galley, Up(z));	/* enclosing galley */
      if( prec_galley == foll_galley )
      {	res = BLOCK;
	break;
      }
    }
  }
  debug1(DGS, DD, "CheckComponentOrder returning %s", Image(res));
  return res;
} /* end CheckComponentOrder */
