/*@z13.c:Object Breaking:BreakJoinedGroup()@**********************************/
/*                                                                           */
/*  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:         z13.c                                                      */
/*  MODULE:       Object Breaking                                            */
/*  EXTERNS:      BreakObject()                                              */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"
#define	broken(x)	back(x, ROWM)	/* OK since no vertical sizes yet    */

#if DEBUG_ON
static int debug_depth = 1;
static int debug_depth_max = 5;
#endif


/*****************************************************************************/
/*                                                                           */
/*  static BreakJoinedGroup(start, stop, m, c, res_back, res_fwd)            */
/*                                                                           */
/*  Break joined group of components of a VCAT, beginning from Child(start)  */
/*  inclusive and ending at Child(stop) inclusive.  Break component m first  */
/*  because it is the widest.                                                */
/*                                                                           */
/*****************************************************************************/

static void BreakJoinedGroup(OBJECT start, OBJECT stop, OBJECT m,
CONSTRAINT *c, FULL_LENGTH *res_back, FULL_LENGTH *res_fwd)
{ OBJECT y = nilobj, link;  FULL_LENGTH b, f, sb, sf;  CONSTRAINT yc;
  debug1(DOB, DD, "[ BreakJoinedGroup(start, stop, m, %s, -, -)",
    EchoConstraint(c));

  /* work out a suitable constraint to apply to each component */
  sb = sf = 0;
  for( link = start;  link != NextDown(stop);  link = NextDown(link) )
  { Child(y, link)
      ;
    if( !is_definite(type(y)) )  continue;
    sb = find_max(sb, back(y, COLM));
    sf = find_max(sf, fwd(y, COLM));
  }
  if( sb <= bc(*c) )
  {
    /* make sure the constraint will accept objects with size (sb, 0) */
    b = sb;
    f = 0;
  }
  else
  {
    /* sb is too wide anyway, so don't worry about it */
    b = 0;
    f = 0;
  }
  SetConstraint(yc, find_min(bc(*c), bfc(*c)-f), bfc(*c), find_min(fc(*c), bfc(*c)-b));

  /* apply this constraint to each component in turn, m first */
  if( m != nilobj )
  {
    debug1(DOB, DD, "  +++BreakJoinedGroup calling first child, yc = %s",
      EchoConstraint(&yc));
    m = BreakObject(m, &yc);
    b = back(m, COLM);
    f = fwd(m, COLM);
    SetConstraint(yc, find_min(bc(yc), bfc(yc)-f), bfc(yc), find_min(fc(yc), bfc(yc)-b));
  }
  else b = f = 0;
  for( link = start;  link != NextDown(stop);  link = NextDown(link) )
  { Child(y, link)
      ;
    if( !is_definite(type(y)) || y == m )  continue;
    debug1(DOB, DD, "  +++BreakJoinedGroup calling child, yc = %s",
      EchoConstraint(&yc));
    y = BreakObject(y, &yc);
    b = find_max(b, back(y, COLM));
    f = find_max(f, fwd(y, COLM));
    SetConstraint(yc, find_min(bc(yc), bfc(yc)-f), bfc(yc), find_min(fc(yc), bfc(yc)-b));
  }
  if( !FitsConstraint(b, f, *c) )
  { debug3(DOB, DD, "  in BreakJoinedGroup: !FitsConstraint(%s, %s, %s)",
      EchoLength(b), EchoLength(f), EchoConstraint(c));
    Error(13, 1, "failed to break column to fit into its available space",
      WARN, m != nilobj ? &fpos(m) : (y != nilobj ? &fpos(y) : no_fpos));
  }
  *res_back = b;  *res_fwd = f;
  debug2(DOB, DD,"] BreakJoinedGroup returning (%s, %s)",
	EchoLength(b), EchoLength(f));
} /* end BreakJoinedGroup */


/*@::BreakVcat()@*************************************************************/
/*                                                                           */
/*  static OBJECT BreakVcat(x, c)                                            */
/*                                                                           */
/*  Break a VCAT to satisfy constraint c.  This is tedious because every     */
/*  group of components between //  ...  // must be broken separately.       */
/*                                                                           */
/*****************************************************************************/

static OBJECT BreakVcat(OBJECT x, CONSTRAINT *c)
{ OBJECT y, link, start_group, m = nilobj;
  FULL_LENGTH b = 0, f = 0, dble_fwd;  CONSTRAINT tc;
  BOOLEAN dble_found;
  debug1(DOB, DD, "[ BreakVcat(x, %s)", EchoConstraint(c));
  assert(Down(x) != x, "BreakVcat: Down(x) == x!" );
  SetConstraint(tc, MAX_FULL_LENGTH, find_min(bfc(*c), fc(*c)), MAX_FULL_LENGTH);
  
  dble_found = FALSE;  dble_fwd = 0;  start_group = nilobj;
  for( link = Down(x);  link != x;  link = NextDown(link) )
  { Child(y, link)
      ;
    if( is_index(type(y)) )  continue;
    if( type(y) == GAP_OBJ )
    { assert( start_group != nilobj, "BreakVcat: start_group == nilobj!" );
      if( !join(gap(y)) )
      {
	/* finish off and break this group */
	if( !FitsConstraint(b, f, tc) )
	  BreakJoinedGroup(start_group, link, m, &tc, &b, &f);
	dble_found = TRUE;
	dble_fwd = find_max(dble_fwd, b + f);
	start_group = nilobj;
	debug1(DOB, DD, "  end group, dble_fwd: %s", EchoLength(dble_fwd));
      }
    }
    else if( start_group == nilobj )
    {	
      /* start new group */
      b = back(y, COLM);  f = fwd(y, COLM);
      start_group = link;  m = y;
      debug2(DOB, DD, "  starting group (b = %s, f = %s):",
	EchoLength(b), EchoLength(f));
      ifdebug(DOB, DD, DebugObject(y));
    }
    else
    {
      /* continue with current group */
      b = find_max(b, back(y, COLM));  f = find_max(f, fwd(y, COLM));
      if( fwd(y, COLM) > fwd(m, COLM) )  m = y;
      debug3(DOB, DD, "  in group%s (b = %s, f = %s):",
	m == y ? " (new max)" : "",
	EchoLength(b), EchoLength(f));
      ifdebug(DOB, DD, DebugObject(y));
    }
  }
  assert( start_group != nilobj, "BreakVcat: start_group == nilobj (2)!" );

  if( dble_found )
  {	
    /* finish off and break this last group, and set sizes of x */
    if( !FitsConstraint(b, f, tc) )
      BreakJoinedGroup(start_group, LastDown(x), m, &tc, &b, &f);
    dble_fwd = find_max(dble_fwd, b + f);
    debug1(DOB, DD, "  ending last group, dble_fwd: %s",EchoLength(dble_fwd));
    back(x, COLM) = 0;  fwd(x, COLM) = find_min(MAX_FULL_LENGTH, dble_fwd);
  }
  else
  {
    /* finish off and break this last and only group, and set sizes of x */
    debug2(DOB, DD, "  BreakVcat ending last and only group (%s, %s)",
	EchoLength(b), EchoLength(f));
    BreakJoinedGroup(start_group, LastDown(x), m, c, &b, &f);
    back(x, COLM) = b;  fwd(x, COLM) = f;
  }

  debug0(DOB, DD, "] BreakVcat returning x:");
  ifdebug(DOB, DD, DebugObject(x));
  debug2(DOB, DD, "  (size is %s, %s)",
	EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
  return x;
} /* end BreakVcat */


/*@::BreakTable()@************************************************************/
/*                                                                           */
/*  static OBJECT BreakTable(x, c)                                           */
/*                                                                           */
/*  Break table (HCAT) x to satisfy constraint c.                            */
/*                                                                           */
/*  Outline of algorithm:                                                    */
/*                                                                           */
/*     bcount = number of components to left of mark;                        */
/*     fcount = no. of components on and right of mark;                      */
/*     bwidth = what back(x) would be if all components had size (0, 0);     */
/*     fwidth = what fwd(x) would be if all components had size (0, 0);      */
/*     Set all components of x to Unbroken (broken(y) holds this flag);      */
/*     while( an Unbroken component of x exists )                            */
/*     {   my = the Unbroken component of x of minimum width;                */
/*         mc = desirable constraint for my (see below);                     */
/*         BreakObject(my, &mc);                                             */
/*         Set my to Broken and update bcount, fcount, bwidth, fwidth        */
/*            to reflect the actual size of my, now broken;                  */
/*     }                                                                     */
/*                                                                           */
/*  The constraint mc is chosen in an attempt to ensure that:                */
/*                                                                           */
/*     a)  Any sufficiently narrow components will not break;                */
/*     b)  All broken components will have the same bfc(mc), if possible;    */
/*     c)  All available space is used.                                      */
/*                                                                           */
/*****************************************************************************/

static OBJECT BreakTable(OBJECT x, CONSTRAINT *c)
{ FULL_LENGTH bwidth, fwidth;	/* running back(x) and fwd(x)		     */
  int    bcount, fcount;	/* running no. of components		     */
  OBJECT mlink = nilobj, my;	/* minimum-width unbroken component	     */
  BOOLEAN ratm = FALSE;		/* TRUE when my has a mark to its right      */
  int    mside;			/* side of the mark my is on: BACK, ON, FWD  */
  FULL_LENGTH msize;		/* size of my (minimal among unbroken)	     */
  CONSTRAINT mc;		/* desirable constraint for my		     */
  OBJECT pg, prec_def;		/* preceding definite object of my           */
  OBJECT sg, succ_def;		/* succeeding definite object of my          */
  FULL_LENGTH pd_extra,sd_extra;/* space availiable for free each side of my */
  FULL_LENGTH av_colsize;	/* the size of each unbroken component       */
				/* if they are all assigned equal width      */
  FULL_LENGTH fwd_max, back_max;/* maximum space available forward of or     */
				/* back of the mark, when columns are even   */
  FULL_LENGTH col_size = 0;	/* the column size actually used in breaking */
  FULL_LENGTH prev_col_size;	/* previous column size (try to keep equal)  */
  FULL_LENGTH beffect, feffect;	/* the amount bwidth, fwidth must increase   */
				/* when my is broken			     */
  OBJECT link, y, prev, g;  FULL_LENGTH tmp, tmp2;

  debug1(DOB, DD, "[ BreakTable( x, %s )", EchoConstraint(c));

  /* Initialise csize, bcount, fcount, bwidth, fwidth and broken(y) */
  bcount = fcount = 0;  bwidth = fwidth = 0;  prev = nilobj;
  prev_col_size = 0;
  Child(y, Down(x))
    ;
  assert( type(y) != GAP_OBJ, "BreakTable: GAP_OBJ!" );
  assert( !is_index(type(y)), "BreakTable: index!" );
  broken(y) = is_indefinite(type(y));
  if( !broken(y) )  prev = y, fcount = 1;

  for( link = NextDown(Down(x));  link != x;  link = NextDown(NextDown(link)) )
  {
    /* find the next gap g and following child y */
    Child(g, link)
      ;
    assert( type(g) == GAP_OBJ, "BreakTable: GAP_OBJ!" );
    assert( NextDown(link) != x, "BreakTable: GAP_OBJ is last!" );
    Child(y, NextDown(link))
      ;

    assert( type(y) != GAP_OBJ, "BreakTable: GAP_OBJ!" );
    assert( !is_index(type(y)), "BreakTable: index!" );
    broken(y) = is_indefinite(type(y));
    if( !broken(y) )
    { if( prev == nilobj )  fcount = 1;
      else if( mark(gap(g)) )
      {	bcount += fcount;
	bwidth += fwidth + MinGap(0, 0, 0, &gap(g));
	fcount  = 1;  fwidth = 0;
      }
      else
      {	fwidth += MinGap(0, 0, 0, &gap(g));
	fcount += 1;
      }
      prev = y;
    }
  }

  /* if column gaps alone are too wide, kill them all */
  if( !FitsConstraint(bwidth, fwidth, *c) )
  {
    debug2(DOB, DD, "column gaps alone too wide: bwidth: %s; fwidth: %s",
       EchoLength(bwidth), EchoLength(fwidth));
    Error(13, 2, "reducing column gaps to 0i (object is too wide)",
      WARN, &fpos(x));
    for( link = Down(x);  link != x;  link = NextDown(link) )
    { Child(g, link)
        ;
      if( type(g) == GAP_OBJ )
      {	SetGap(gap(g), nobreak(gap(g)), mark(gap(g)), join(gap(g)),
	  FIXED_UNIT, EDGE_MODE, 0);
      }
    }
    bwidth = fwidth = 0;
  }

  /* break each column, from smallest to largest */
  while( bcount + fcount > 0 && FitsConstraint(bwidth, fwidth, *c) )
  {
    debug2(DOB, DD, "bcount: %d;  bwidth: %s", bcount, EchoLength(bwidth));
    debug2(DOB, DD, "fcount: %d;  fwidth: %s", fcount, EchoLength(fwidth));

    /* find a minimal-width unbroken component my */
    my = nilobj;  msize = size(x, COLM);       /* an upper bound for size(y) */
    for( link = Down(x);  ;  link = NextDown(link) )
    { Child(y, link)
        ;
      assert( type(y) != GAP_OBJ, "BreakTable: type(y) == GAP_OBJ!" );
      if( !broken(y) && (size(y, COLM) < msize || my == nilobj) )
      {	msize = size(y, COLM);
	my = y;  mlink = link;
	ratm = FALSE;
      }

      /* next gap */
      link = NextDown(link);
      if( link == x )  break;
      Child(g, link)
        ;
      assert( type(g) == GAP_OBJ, "BreakTable: type(g) != GAP_OBJ!" );
      if( mark(gap(g)) )  ratm = TRUE;
    }

    /* find neighbouring definite objects and resulting pd_extra and sd_extra */
    SetNeighbours(mlink, ratm, &pg, &prec_def, &sg, &succ_def, &mside);
    debug2(DOB, DD, "my (%s): %s", Image(mside), EchoObject(my));
    pd_extra = pg == nilobj ? 0 :
      ExtraGap(broken(prec_def) ? fwd(prec_def,COLM) : 0, 0, &gap(pg), BACK);
    sd_extra = sg == nilobj ? 0 :
      ExtraGap(0, broken(succ_def) ? back(succ_def,COLM) : 0, &gap(sg), FWD);
    debug2(DOB, DD, "pd_extra:   %s;  sd_extra:      %s",
		EchoLength(pd_extra), EchoLength(sd_extra) );

    /* calculate desirable constraints for my */
    av_colsize = (bfc(*c) - bwidth - fwidth) / (bcount + fcount);
    debug1(DOB, DD, "av_colsize = %s", EchoLength(av_colsize));
    debug1(DOB, DD, "prev_col_size = %s", EchoLength(prev_col_size));
    switch( mside )
    {

      case BACK:
      
	back_max = find_min(bc(*c), bwidth + av_colsize * bcount);
	col_size = (back_max - bwidth) / bcount;
	if( col_size > prev_col_size && col_size - prev_col_size < PT )
	  col_size = prev_col_size;
	SetConstraint(mc,
	  find_min(MAX_FULL_LENGTH, col_size + pd_extra),
	  find_min(MAX_FULL_LENGTH, col_size + pd_extra + sd_extra),
	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
	break;


      case ON:
      
	fwd_max = find_min(fc(*c), fwidth + av_colsize * fcount);
	col_size = (fwd_max - fwidth) / fcount;
	if( col_size > prev_col_size && col_size - prev_col_size < PT )
	  col_size = prev_col_size;
	SetConstraint(mc,
	  find_min(MAX_FULL_LENGTH, pd_extra + back(my, COLM)),
	  find_min(MAX_FULL_LENGTH, pd_extra + back(my, COLM) + col_size + sd_extra),
	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
	break;


      case FWD:
      
	fwd_max = find_min(fc(*c), fwidth + av_colsize * fcount);
	col_size = (fwd_max - fwidth) / fcount;
	if( col_size > prev_col_size && col_size - prev_col_size < PT )
	  col_size = prev_col_size;
	SetConstraint(mc,
	  find_min(MAX_FULL_LENGTH, col_size + pd_extra),
	  find_min(MAX_FULL_LENGTH, col_size + pd_extra + sd_extra),
	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
	break;


      default:
      
	assert(FALSE, "BreakTable: mside");
	break;
    }
    debug1(DOB, DD, "col_size = %s", EchoLength(col_size));
    prev_col_size = col_size;

    /* now break my according to these constraints, and accept it */
    debug2(DOB, DD, "  calling BreakObject(%s, %s)", EchoObject(my),
      EchoConstraint(&mc));
    my = BreakObject(my, &mc);  broken(my) = TRUE;

    /* calculate the effect of accepting my on bwidth and fwidth */
    if( pg != nilobj )
    { tmp = broken(prec_def) ? fwd(prec_def, COLM) : 0;
      beffect = MinGap(tmp, back(my, COLM), fwd(my, COLM), &gap(pg)) -
	        MinGap(tmp, 0,             0,            &gap(pg));
    }
    else beffect = back(my, COLM);

    if( sg != nilobj )
    { tmp = broken(succ_def) ? back(succ_def, COLM) : 0;
      tmp2 = broken(succ_def) ? fwd(succ_def, COLM) : 0;
      feffect = MinGap(fwd(my, COLM), tmp, tmp2, &gap(sg)) -
	        MinGap(0,            tmp, tmp2, &gap(sg));
    }
    else feffect = fwd(my, COLM);

    switch( mside )
    {
	case BACK:	bwidth += beffect + feffect;
			bcount--;
			break;
	
	case ON:	bwidth += beffect;  fwidth += feffect;
			fcount--;
			break;

	case FWD:	fwidth += beffect + feffect;
			fcount--;
			break;
	
	default:	assert(FALSE, "BreakTable: mside");
			break;
    }

  } /* end while */

  back(x, COLM) = bwidth;
  fwd(x, COLM) = fwidth;

  debug2(DOB, DD,  "] BreakTable returning %s,%s; x =",
    EchoLength(bwidth), EchoLength(fwidth));
  ifdebug(DOB, DD, DebugObject(x));
  return x;
} /* end BreakTable */


/*@::BreakObject()@***********************************************************/
/*                                                                           */
/*  OBJECT BreakObject(x, c)                                                 */
/*                                                                           */
/*  Break lines of object x so that it satisfies constraint c.               */
/*                                                                           */
/*****************************************************************************/

OBJECT BreakObject(OBJECT x, CONSTRAINT *c)
{ OBJECT link, y;  CONSTRAINT yc;  FULL_LENGTH f;  BOOLEAN junk;
  debugcond4(DOB, D, debug_depth++ < debug_depth_max,
    "%*s[ BreakObject(%s %d)", (debug_depth-1)*2, " ", Image(type(x)), (int) x);
  debug4(DOB, DD,  "[ BreakObject(%s (%s,%s),  %s), x =",
    Image(type(x)), EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)),
    EchoConstraint(c));
  ifdebug(DOB, DD, DebugObject(x));

  /* if constraint is negative (should really be never), replace with empty */
  if( !(bc(*c)>=0 && bfc(*c)>=0 && fc(*c)>=0) )
  {
    Error(13, 11, "replacing with empty object: negative size constraint %s,%s,%s",
      WARN, &fpos(x), EchoLength(bc(*c)), EchoLength(bfc(*c)), EchoLength(fc(*c)));
    y = MakeWord(WORD, STR_EMPTY, &fpos(x));
    back(y, COLM) = fwd(y, COLM) = 0;
    ReplaceNode(y, x);
    DisposeObject(x);
    x = y;
    debugcond6(DOB, D, --debug_depth < debug_depth_max,
      "%*s] BreakObject(%s %d) (neg!) = (%s, %s)", debug_depth*2, " ",
      Image(type(x)), (int) x, EchoLength(back(x, COLM)),
      EchoLength(fwd(x, COLM)));
    debug0(DOB, DD, "] BreakObject returning (negative constraint).");
    return x;
  }

  /* if no breaking required, return immediately */
  if( FitsConstraint(back(x, COLM), fwd(x, COLM), *c) )
  { debug0(DOB, DD, "] BreakObject returning (fits).");
    debugcond6(DOB, D, --debug_depth < debug_depth_max,
      "%*s] BreakObject(%s %d) (fits) = (%s, %s)", debug_depth*2, " ",
      Image(type(x)), (int) x, EchoLength(back(x, COLM)),
      EchoLength(fwd(x, COLM)));
    return x;
  }

  switch( type(x) )
  {

    case ROTATE:
    
      if( BackEnd->scale_avail && InsertScale(x, c) )
      {
	Parent(x, Up(x));
	Error(13, 3, "%s object scaled horizontally by factor %.2f (too wide)",
	  WARN, &fpos(x), KW_ROTATE, (float) bc(constraint(x)) / SF );
      }
      else
      { Error(13, 4, "%s deleted (too wide; cannot break %s)",
	  WARN, &fpos(x), KW_ROTATE, KW_ROTATE);
        y = MakeWord(WORD, STR_EMPTY, &fpos(x));
        back(y, COLM) = back(y, ROWM) = fwd(y, COLM) = fwd(y, ROWM) = 0;
        ReplaceNode(y, x);
        DisposeObject(x);
        x = y;
      }
      break;


    case SCALE:

      InvScaleConstraint(&yc, bc(constraint(x)), c);
      Child(y, Down(x))
        ;
      y = BreakObject(y, &yc);
      back(x, COLM) = (back(y, COLM) * bc(constraint(x))) / SF;
      fwd(x, COLM) =  (fwd(y, COLM)  * bc(constraint(x))) / SF;
      break;


    case KERN_SHRINK:

      /* not really accurate, but there you go */
      Child(y, LastDown(x))
        ;
      y = BreakObject(y, c);
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      break;


    case WORD:
    case QWORD:
    
      if( word_hyph(x) )
      {
	/* create an ACAT with the same size as x */
	New(y, ACAT);
	FposCopy(fpos(y), fpos(x));
	back(y, COLM) = back(x, COLM);
	fwd(y, COLM) = fwd(x, COLM);
	back(y, ROWM) = back(x, ROWM);
	fwd(y, ROWM) = fwd(x, ROWM);

	/* set ACAT's save_style; have to invent a line_gap, unfortunately */
	SetGap(line_gap(save_style(y)), FALSE, FALSE, FALSE, FIXED_UNIT,
	  MARK_MODE, 1.1 * FontSize(word_font(x), x));
	SetGap(space_gap(save_style(y)), FALSE, FALSE, TRUE, FIXED_UNIT,
	  EDGE_MODE, 0);
	hyph_style(save_style(y)) = HYPH_ON;
	fill_style(save_style(y)) = FILL_ON;
	display_style(save_style(y)) = DISPLAY_LEFT;
	small_caps(save_style(y)) = FALSE;
	font(save_style(y)) = word_font(x);
	colour(save_style(y)) = word_colour(x);
	underline_colour(save_style(y)) = word_underline_colour(x);
	texture(save_style(y)) = word_texture(x);
	outline(save_style(y)) = word_outline(x);
	language(save_style(y)) = word_language(x);
	baselinemark(save_style(y)) = word_baselinemark(x);
	strut(save_style(y)) = word_strut(x);
	ligatures(save_style(y)) = word_ligatures(x);
	debug3(DOF, DD, "  in BreakObject y %s %s %s",
	  EchoStyle(&save_style(y)), Image(type(y)), EchoObject(y));

	/* enclose x in the ACAT and try breaking (i.e. filling) it */
	ReplaceNode(y, x);
	Link(y, x);
	x = y;
	debug3(DOF, DD, "  in BreakObject x %s %s %s",
	  EchoStyle(&save_style(x)), Image(type(x)), EchoObject(x));
	x = BreakObject(x, c);
      }
      else if( BackEnd->scale_avail && InsertScale(x, c) )
      { OBJECT tmp;
	tmp = x;
	Parent(x, Up(x));
	Error(13, 5, "word %s scaled horizontally by factor %.2f (too wide)",
	  WARN, &fpos(x), string(tmp), (float) bc(constraint(x)) / SF);
      }
      else
      { Error(13, 6, "word %s deleted (too wide)", WARN, &fpos(x), string(x));
        y = MakeWord(WORD, STR_EMPTY, &fpos(x));
        back(y, COLM) = fwd(y, COLM) = 0;
        ReplaceNode(y, x);
        DisposeObject(x);
        x = y;
      }
      break;


    case WIDE:
    
      MinConstraint(&constraint(x), c);
      Child(y, Down(x))
        ;
      y = BreakObject(y, &constraint(x));
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      EnlargeToConstraint(&back(x, COLM), &fwd(x, COLM), &constraint(x));
      break;


    case INCGRAPHIC:
    case SINCGRAPHIC:

      if( BackEnd->scale_avail && InsertScale(x, c) )
      {
	Parent(x, Up(x));
	Error(13, 7, "%s scaled horizontally by factor %.2f (too wide)",
	  WARN, &fpos(x),
	  type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
	  (float) bc(constraint(x)) / SF);
      }
      else
      { Error(13, 8, "%s deleted (too wide)", WARN, &fpos(x),
	  type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC);
        y = MakeWord(WORD, STR_EMPTY, &fpos(x));
        back(y, COLM) = fwd(y, COLM) = 0;
        ReplaceNode(y, x);
        DisposeObject(x);
        x = y;
      }
      break;


    case HMIRROR:

      FlipConstraint(yc, *c);
      Child(y, Down(x))
        ;
      y = BreakObject(y, &yc);
      back(x, COLM) = fwd(y, COLM);
      fwd(x, COLM) = back(y, COLM);
      break;


    case HIGH:
    case VMIRROR:
    case VSCALE:
    case VCOVER:
    case VSHIFT:
    case HCONTRACT: 
    case VCONTRACT:
    case HLIMITED: 
    case VLIMITED:
    case HEXPAND: 
    case VEXPAND:
    case ONE_COL:
    case ONE_ROW:
    case HSPANNER:
    
      assert( Down(x) == LastDown(x), "BreakObject: downs!" );
      Child(y, Down(x))
        ;
      y = BreakObject(y, c);
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      break;


    case BACKGROUND:

      Child(y, Down(x))
        ;
      y = BreakObject(y, c);
      Child(y, LastDown(x))
        ;
      y = BreakObject(y, c);
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      break;


    case START_HVSPAN:
    case START_HSPAN:
    case START_VSPAN:
    case HSPAN:
    case VSPAN:

      /* these all have size zero except the last one, so if we get to  */
      /* this point we must be at the last column and need to break it. */
      /* this is done just by setting its size to zero, unless it is    */
      /* the last column in which case it claims everything that is     */
      /* going; the real break is deferred to the first ROWM touch,     */
      /* when we know that all contributing columns have been broken    */
      /* unless the child is not a spanner, in which case it's @OneCol  */
      Child(y, Down(x))
        ;
      if( type(y) != HSPANNER )
      {
        y = BreakObject(y, c);
        back(x, COLM) = back(y, COLM);
        fwd(x, COLM) = fwd(y, COLM);
      }
      else
      {
        back(x, COLM) = 0;
        fwd(x, COLM) = find_min(bfc(*c), fc(*c));
      }
      break;


    case HSHIFT:

      Child(y, Down(x))
        ;
      f = FindShift(x, y, COLM);
      SetConstraint(yc,
	find_min(bc(*c), bfc(*c)) - f, bfc(*c), find_min(fc(*c), bfc(*c)) + f);
      BreakObject(y, &yc);
      f = FindShift(x, y, COLM);
      back(x, COLM) = find_min(MAX_FULL_LENGTH, find_max(0, back(y, COLM) + f));
      fwd(x, COLM)  = find_min(MAX_FULL_LENGTH, find_max(0, fwd(y, COLM)  - f));
      break;


    case END_HEADER:
    case CLEAR_HEADER:

      /* these have size zero anyway, so not likely to reach this point */
      break;


    case BEGIN_HEADER:
    case SET_HEADER:
    
      /* multiple copies, remember */
      for( link = NextDown(Down(x));  link != x;  link = NextDown(link) )
      {
        Child(y, link)
          ;
        y = BreakObject(y, c);
        back(x, COLM) = back(y, COLM);
        fwd(x, COLM) = fwd(y, COLM);
      }
      debug3(DOB, D, "BreakObject(%s, COLM) = (%s, %s)", Image(type(x)),
	EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
      break;


    case PLAIN_GRAPHIC:
    case GRAPHIC:
    case LINK_SOURCE:
    case LINK_DEST:
    case LINK_DEST_NULL:
    case LINK_URL:
    
      Child(y, LastDown(x))
        ;
      y = BreakObject(y, c);
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      break;


    case SPLIT:
    
      Child(y, DownDim(x, COLM))
        ;
      y = BreakObject(y, c);
      back(x, COLM) = back(y, COLM);
      fwd(x, COLM) = fwd(y, COLM);
      break;


    case ACAT:
    
      if( back(x, COLM) > 0 )
      { int sz;  OBJECT rpos;
	/* shift the column mark of x to the left edge */
	sz = size(x, COLM);
	fwd(x, COLM) = find_min(MAX_FULL_LENGTH, sz);
	back(x, COLM) = 0;
	rpos = x;
	for( link = Down(x);  link != x;  link = NextDown(link) )
	{ Child(y, link)
	    ;
	  if( type(y) == GAP_OBJ && mark(gap(y)) )
	  { mark(gap(y)) = FALSE;
	    rpos = y;
	  }
	}
	if( FitsConstraint(back(x, COLM), fwd(x, COLM), *c) )
	{ Error(13, 9, "column mark of unbroken paragraph moved left",
	    WARN, &fpos(rpos));
	  break;
	}
	Error(13, 10, "column mark of paragraph moved left before breaking",
	  WARN, &fpos(rpos));
	ifdebug(DOB, DD, DebugObject(x));
      }
      x = FillObject(x, c, nilobj, TRUE, TRUE, FALSE, &junk);
      break;


    case HCAT:
    
      x = BreakTable(x, c);
      break;


    case COL_THR:
    
      BreakJoinedGroup(Down(x), LastDown(x), nilobj, c,
	&back(x,COLM), &fwd(x,COLM));
      break;


    case VCAT:
    
      x = BreakVcat(x, c);
      break;
			

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

  }
  assert( back(x, COLM) >= 0, "BreakObject: back(x, COLM) < 0!" );
  assert( fwd(x, COLM) >= 0, "BreakObject: fwd(x, COLM) < 0!" );
  debugcond6(DOB, D, --debug_depth < debug_depth_max,
    "%*s] BreakObject(%s %d) = (%s, %s)", debug_depth*2, " ", Image(type(x)),
    (int) x, EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
  debug2(DOB, DD,  "] BreakObject returning %s,%s, x =",
    EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
  ifdebug(DOB, DD,  DebugObject(x));
  return x;
} /* end BreakObject */
