/*@z21.c:Galley Maker:SizeGalley()@*******************************************/
/*                                                                           */
/*  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:         z21.c                                                      */
/*  MODULE:       Galley Maker                                               */
/*  EXTERNS:      SizeGalley()                                               */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"

/*****************************************************************************/
/*                                                                           */
/*  SizeGalley(hd, env, rows, joined, nonblock, trig, style, c, target,      */
/*                                     dest_index, recs, inners, enclose)    */
/*                                                                           */
/*  Convert unsized galley hd into sized format.  The input parameters are:  */
/*                                                                           */
/*    hd          the galley to be converted                                 */
/*    env         its environment (needs to be "held" while manifesting)     */
/*    rows        TRUE if the resulting galley may have more than one row    */
/*    joined      TRUE if the resulting galley must be simply joined         */
/*    nonblock    Set the non_blocking() field of RECEPTIVEs to this value   */
/*    trig        TRUE if indefinites of hd may trigger external galleys     */
/*    *style      The initial style                                          */
/*    *c          the width constraint hd should conform to                  */
/*    target      if non-nilobj, expand indefinite objects to reveal a       */
/*                @Galley within this symbol                                 */
/*    enclose     If non-nilobj, enclose any @Galley symbol encountered      */
/*                during manifesting by this symbol.                         */
/*                                                                           */
/*  The output parameters, in addition to the converted hd, are:             */
/*                                                                           */
/*    dest_index  the index of the @Galley found within target, if any       */
/*    recs        list of all RECURSIVE indexes found (or nilobj if none)    */
/*    inners      list of all UNATTACHED indexes found (or nilobj if none),  */
/*                not including any that come after the target or InputSym.  */
/*                                                                           */
/*****************************************************************************/

void SizeGalley(OBJECT hd, OBJECT env, BOOLEAN rows, BOOLEAN joined,
BOOLEAN nonblock, BOOLEAN trig, STYLE *style, CONSTRAINT *c, OBJECT target,
OBJECT *dest_index, OBJECT *recs, OBJECT *inners, OBJECT enclose)
{ OBJECT y, link, z, crs, t, tlink, zlink, tmp, why;
  OBJECT extras, tmp1, tmp2, bt[2], ft[2], hold_env;
  BOOLEAN after_target;
  
  assert( type(hd) == HEAD && Down(hd) != hd, "SizeGalley: precondition!" );
  assert( !sized(hd), "SizeGalley: already sized!" );
  debug6(DGM, D, "SizeGalley(%s, -, %s, %s, %s, %s, -, %s, -, -, -), hd =",
	SymName(actual(hd)), bool_str(rows), bool_str(joined), bool_str(nonblock),
	bool_str(trig), EchoConstraint(c));
  debug1(DGM, DD, "  env = %s", EchoObject(env));
  ifdebug(DGM, D, DebugObject(hd));

  /* manifest the child of hd, making sure it is simply joined if required */
  Child(y, Down(hd))
    ;
  tmp1 = target;
  tmp2 = enclose;
  crs = nilobj;
  bt[COLM] = ft[COLM] = bt[ROWM] = ft[ROWM] = nilobj;
  New(hold_env, ACAT);  Link(hold_env, env);
  if( AllowCrossDb && type(y) == CLOSURE && has_optimize(actual(y))
      && FindOptimize(y, env) )
  {
    SetOptimize(hd, style);
  }
  debug2(DOM, D, "[ calling Manifest(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));
  debug2(DOB, D, "[ calling Manifest(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));
  if( joined )
  { New(bt[COLM], THREAD);  New(ft[COLM], THREAD);
    debug0(DGM, DD, "SizeGalley calling Manifest (joined)");
    y = Manifest(y, env, style, bt, ft, &tmp1, &crs, TRUE, must_expand(hd),
      &tmp2, FALSE);
    assert( Down(bt[COLM]) != bt[COLM] && Down(ft[COLM]) != ft[COLM],
	"SizeGalley: threads!" );
    Child(tmp1, Down(bt[COLM]))
      ;
    Child(tmp2, Down(ft[COLM]))
      ;
    if( Down(bt[COLM]) != LastDown(bt[COLM]) ||
	  Down(ft[COLM]) != LastDown(ft[COLM]) || tmp1 != tmp2 )
      Error(21, 1, "galley %s must have just one column mark",
	FATAL, &fpos(y), SymName(actual(hd)) );
    DisposeObject(bt[COLM]);  DisposeObject(ft[COLM]);
  }
  else
  { debug0(DGM, DD, "SizeGalley calling Manifest (not joined)");
    y = Manifest(y, env, style, bt, ft, &tmp1, &crs, TRUE, must_expand(hd),
      &tmp2, FALSE);
  }
  debug2(DOM, D, "] returning Manifest(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));
  debug2(DOB, D, "] returning Manifest(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));
  DisposeObject(hold_env);
  debug0(DGM, DD, "SizeGalley: after manifesting, hd =");
  ifdebug(DGM, DD, DebugObject(hd));

  /* horizontally size hd */
  debug0(DGM, DD, "SizeGalley horizontally sizing hd:");
  New(extras, ACAT);
  debug2(DSF, D, "[ calling MinSize(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));
  y = MinSize(y, COLM, &extras);
  debug2(DSF, D, "] returning MinSize(%s) from SizeGalley(%s)",
    Image(type(y)), SymName(actual(hd)));

  /* break hd if vertical galley */
  if( gall_dir(hd) == ROWM )
  {
    CopyConstraint(constraint(hd), *c);
    debug0(DGM, DD, "SizeGalley calling BreakObject:");
    debug2(DOB, D, "[ calling BreakObject(%s) from SizeGalley(%s)",
      Image(type(y)), SymName(actual(hd)));
    y = BreakObject(y, c);
    debug2(DOB, D, "] returning BreakObject(%s) from SizeGalley(%s)",
      Image(type(y)), SymName(actual(hd)));
    if( !FitsConstraint(back(y, COLM), fwd(y, COLM), *c) )
      Error(21, 13, "%s,%s object too wide for available space",
        FATAL, &fpos(y), EchoLength(back(y, COLM)), EchoLength(fwd(y, COLM)));
    back(hd, COLM) = back(y, COLM);
    fwd(hd, COLM)  = fwd(y, COLM);
    assert( FitsConstraint(back(hd, COLM), fwd(hd, COLM), *c),
	"SizeGalley: BreakObject failed to fit!" );
    debug2(DSF, D, "MinSize(hd, COLM) = %s,%s",
	  EchoLength(back(hd, COLM)), EchoLength(fwd(hd, COLM)) );
  }

  /* hyphenate hd if horizontal optimal galley says so */
  else if( opt_components(hd) != nilobj && opt_hyph(hd) && type(y) == ACAT )
  { debug0(DOG, D, "SizeGalley calling Hyphenate()");
    y = Hyphenate(y);
  }

  /* get the rows of hd to the top level, if required */
  seen_nojoin(hd) = FALSE;
  if( rows )
  { /* OBJECT prev_gap = nilobj; */
    debug0(DGM, DD, "SizeGalley cleaning up rows of hd:");
    for( link = hd;  NextDown(link) != hd;  link = NextDown(link) )
    { Child(y, NextDown(link))
        ;
      switch( type(y) )
      {
	case GAP_OBJ:

          debug2(DGM, DD, "  cleaning %s: %s", Image(type(y)), EchoObject(y));
	  /* prev_gap = y; */
	  if( !join(gap(y)) )  seen_nojoin(hd) = TRUE;
	  break;


	case VCAT:
	  
          debug1(DGM, DD, "  cleaning %s:", Image(type(y)));
          ifdebug(DGM, DD, DebugObject(y));
	  if( gall_dir(hd) == ROWM )
	  { TransferLinks(Down(y), y, Up(y));
	    DisposeChild(Up(y));
	    link = PrevDown(link);
	  }
	  break;


	case ACAT:
	  
          debug2(DGM, DD, "  cleaning %s: %s", Image(type(y)), EchoObject(y));
	  if( gall_dir(hd) == COLM )
	  { TransferLinks(Down(y), y, Up(y));
	    DisposeChild(Up(y));
	    link = PrevDown(link);
	  }
	  break;


	case SPLIT:
	  
          debug1(DGM, DD, "  cleaning %s:", Image(type(y)));
          ifdebug(DGM, DD, DebugObject(y));
	  assert(Up(y)==LastUp(y), "SizeGalley COL_THR: Up(y)!=LastUp(y)!");
	  Child(z, DownDim(y, ROWM))
	    ;
	  if( is_indefinite(type(z)) )
	  {
	    debug1(DGT, D, "SizeGalley setting external_ver(%s) to TRUE (a)",
	      EchoObject(z));
	    external_ver(z) = TRUE;
	  }
	  else if( type(z) == VCAT )
	  { OBJECT hor, thor, clink, dlink;
	    Child(hor, DownDim(y, COLM))
	      ;
	    assert( type(hor) == COL_THR, "SizeGalley: missing COL_THR!" );
	    Parent(thor, UpDim(z, COLM))
	      ;
	    assert( hor == thor, "SizeGalley/SPLIT: hor != thor!" );
	    clink = DownDim(y, COLM);
	    dlink = UpDim(z, COLM);
	    for( tlink = LastDown(z);  tlink != z;  tlink = PrevDown(tlink) )
	    { Child(t, tlink)
	        ;
	      if( type(t) == GAP_OBJ )
	      { Link(NextDown(link), t);
	      }
	      else
	      {	New(tmp, SPLIT);
		back(tmp, COLM) = back(hor, COLM);
		fwd(tmp, COLM) = fwd(hor, COLM);
		Link(NextDown(link), tmp);
		Link(tmp, NextUp(clink));
		Link(NextDown(dlink), t);
		Link(tmp, t);
	      }
	    }
	    /* will be done by DisposeChild below DeleteLink(dlink); */
	    assert(Up(y)==LastUp(y), "SizeGalley COL_THR: Up(y) != LastUp(y)!");
	    DisposeChild(Up(y));
	    link = PrevDown(link);
	  }
	  break;


	case CLOSURE:
	case HEAD:
	  
          debug2(DGM, DD, "  cleaning %s: %s", Image(type(y)), EchoObject(y));
	  if( gall_dir(hd) == COLM )
	    external_hor(y) = TRUE;
	  else
	  {
	    debug1(DGT, D, "SizeGalley setting external_ver(%s) to TRUE (b)",
	      EchoObject(y));
	    external_ver(y) = TRUE;
	  }
	  break;


	default:
	  
          debug2(DGM, DD, "  cleaning %s: %s", Image(type(y)), EchoObject(y));
	  break;
      }
    }
  }

  /* determine a scale factor for {} @Scale objects */
  /* NB AdjustSize cannot be done correctly until after seen_nojoin is set */
  for( link = Down(extras);  link != extras;  link = NextDown(link) )
  { Child(y, link)
      ;
    if( type(y) == SCALE_IND )
    {
      /* check that all is in order */
      CONSTRAINT zc;  OBJECT t;  FULL_LENGTH b, f;
      z = actual(y);
      assert( type(z) == SCALE, "SizeObject: type(z) != SCALE!" );
      assert( bc(constraint(z)) == 0, "SizeObject: bc(constraint(z)) != 0" );
      assert( Down(z) != z, "SizeObject SCALE: Down(z) == z!" );
      Child(t, Down(z))
        ;

      /* use @Scale COLM size constraint to determine a suitable scale factor */
      /* check that @Scale is not in a horizontal galley */
      if( gall_dir(hd) == COLM )
      { Error(21, 2, "%s with unspecified scale factor in horizontal galley",
	  FATAL, &fpos(z), KW_SCALE);
      }

      Constrained(z, &zc, COLM, &why);
      debug2(DGM, DD, "Constrained(%s, -, COLM) = %s", EchoObject(z),
	EchoConstraint(&zc));
      if( !constrained(zc) )
      { Error(21, 3, "replacing infinite scale factor (unconstrained width) by 1.0",
	  WARN, &fpos(z));
	bc(constraint(z)) = fc(constraint(z)) = 1 * SF;
      }
      else if( size(t, COLM) == 0 )
      { Error(21, 4, "replacing infinite scale factor (zero width object) by 1.0",
	  WARN, &fpos(z));
	bc(constraint(z)) = fc(constraint(z)) = 1 * SF;
      }
      else if( (float) bfc(zc) / size(t, COLM) > 100.0 )
      { Error(21, 5, "replacing very large scale factor (over 100) by 1.0",
	  WARN, &fpos(z));
	bc(constraint(z)) = fc(constraint(z)) = 1 * SF;
      }
      else if( (float) bfc(zc) / size(t, COLM) < 0.01 )
      { if( bfc(zc) == 0 )
	  Error(21, 6, "object deleted (scale factor is zero)",
	    WARN, &fpos(z));
	else
	  Error(21, 7, "object deleted (scale factor is smaller than 0.01)",
	    WARN, &fpos(z));
	bc(constraint(z)) = fc(constraint(z)) = 1 * SF;
	tmp = MakeWord(WORD, STR_EMPTY, &fpos(t));
	back(tmp, COLM) = fwd(tmp, COLM) = 0;
	back(tmp, ROWM) = fwd(tmp, ROWM) = 0;
	word_font(tmp) = word_colour(tmp) = word_language(tmp) = 0;
	word_underline_colour(tmp) = 0;
	word_texture(tmp) = 1;
	word_baselinemark(tmp) = FALSE;
	word_strut(tmp) = FALSE;
	word_ligatures(tmp) = TRUE;
	word_outline(tmp) = FALSE;
	word_hyph(tmp) = FALSE;
	ReplaceNode(tmp, t);
	DisposeObject(t);
	t = tmp;
      }
      else if( bfc(constraint(z)) == -1 )
      {
	/* we want to scale down but not up */
	if( size(t, COLM) > bfc(zc) )
	  bc(constraint(z)) = fc(constraint(z)) = (bfc(zc) * SF)/size(t, COLM);
	else
	  bc(constraint(z)) = fc(constraint(z)) = 1 * SF;
      }
      else bc(constraint(z)) = fc(constraint(z)) = (bfc(zc) * SF)/size(t, COLM);

      /* calculate scaled size and adjust */
      b = (back(t, COLM) * fc(constraint(z))) / SF;
      f = (fwd(t, COLM) * fc(constraint(z))) / SF;
      debug0(DSA, D, "  calling AdjustSize from SizeGalley for SCALE_IND COLM");
      debug3(DGM, DD, "AdjustSize(%s, %s, %s, COLM)", EchoObject(z),
	EchoLength(b), EchoLength(f));
      AdjustSize(z, b, f, COLM);

      /* if already vertically sized (because inside @Rotate) adjust that */
      if( vert_sized(z) )
      { b = (back(t, ROWM) * fc(constraint(z))) / SF;
	f = (fwd(t, ROWM) * fc(constraint(z))) / SF;
	debug0(DSA, D, "  calling AdjustSize from SizeGalley for SCALE_IND ROWM");
	debug4(DGM, DD, "AdjustSize(%s, %s, %s, %s)", EchoObject(z),
	  EchoLength(b), EchoLength(f), dimen(ROWM));
	AdjustSize(z, b, f, ROWM);
      }
    }
  }
  DisposeObject(extras);

  /* size the rows of hd and attach indices where needed */
  debug0(DGM, DD, "  SizeGalley calling MinSize(ROWM):");
  debug0(DGM, DD, "SizeGalley sizing rows of hd =");
  ifdebug(DGM, DD, DebugObject(hd));
  *recs = *inners = *dest_index = nilobj;
  after_target = FALSE;
  for( link = Down(hd);  link != hd;  link = NextDown(link) )
  { Child(y, link)
      ;

    if( type(y) == GAP_OBJ || is_index(type(y)) )  continue;
    debug0(DGM, DDD, "  ROWM sizing:");
    ifdebug(DGM, DDD, DebugObject(y));
    New(extras, ACAT);
    y = MinSize(y, ROWM, &extras);
    debug3(DSF, DD, "MinSize( %s , ROWM ) = %s,%s", EchoObject(y),
	  EchoLength(back(y, ROWM)), EchoLength(fwd(y, ROWM)) );
    debug0(DGM, DDD, "  ROWM result:");
    ifdebug(DGM, DDD, DebugObject(y));

    /* now attach indexes in front of y */
    for( zlink = Down(extras);  zlink != extras;  zlink = NextDown(zlink) )
    { Child(z, zlink)
        ;
      blocked(z) = FALSE;
      /* debug1(DCR, DD, "  extra: %s", EchoObject(z)); */
      debug2(DGM, DD, "  extra%s: %s",
	after_target ? " after_target" : "", EchoObject(z));
      switch( type(z) )
      {
	case RECEPTIVE:

	  /* debug2(DCR, DD, "  ... uses_ext  = %s, trig = %s",
	    bool_str(uses_extern_target(actual(actual(z)))), bool_str(trig)); */
	  trigger_externs(z) = uses_extern_target(actual(actual(z))) && trig;
	  non_blocking(z) = nonblock;
	  if( actual(actual(z)) == GalleySym || actual(actual(z)) == ForceGalleySym )
	    *dest_index = z;
	  if( actual(actual(z)) == GalleySym || actual(actual(z)) == ForceGalleySym
	    || actual(actual(z)) == InputSym )
	    after_target = TRUE;
	  break;


	case RECURSIVE:

	  if( *recs == nilobj )  New(*recs, ACAT);
	  Link(*recs, z);
	  break;


	case UNATTACHED:

	  if( !after_target )	/* *** new semantics *** */
	  { if( *inners == nilobj )  New(*inners, ACAT);
	    Link(*inners, z);
	  }
	  Child(tmp, Down(z))
	    ;
	  debug2(DGA, D, "SizeGalley %s%s", actual(tmp) == NULL ? "null" :
	    SymName(actual(tmp)), after_target ? " (after_target)" : "");
	  break;

		
	case SCALE_IND:
	case EXPAND_IND:
	case GALL_PREC:
	case GALL_FOLL:
	case GALL_FOLL_OR_PREC:
	case GALL_TARG:
	case CROSS_PREC:
	case CROSS_FOLL:
	case CROSS_FOLL_OR_PREC:
	case CROSS_TARG:
	case PAGE_LABEL_IND:

	  debug1(DCR, DD, "  SizeGalley: %s", EchoObject(z));
	  break;


	case COVER_IND:

	  /* adjust size of the COVER object, change it to @Scale etc. */
	  { OBJECT cover, prnt, chld;  int dirn, thr_type, ok1, ok2, sf,subst, esubst;
	    float sf1, sf2;  CONSTRAINT c;  FULL_LENGTH b, f;
	    cover = actual(z);
	    if( type(cover) == HCOVER )
	    { dirn = COLM;
	      thr_type = COL_THR;
	      ok1 = VCAT;
	      ok2 = VCAT;
	      subst = HSCALE;
	      esubst = ONE_COL;
	    }
	    else
	    { dirn = ROWM;
	      thr_type = ROW_THR;
	      ok1 = ACAT;
	      ok2 = HCAT;
	      subst = VSCALE;
	      esubst = ONE_ROW;
	    }
	    Parent(prnt, UpDim(cover, dirn))
	      ;
	    while( type(prnt) == SPLIT || type(prnt) == thr_type )
	      Parent(prnt, UpDim(prnt, dirn))
	        ;
	    Child(chld, Down(cover))
	      ;
	    if( type(prnt) != ok1 && type(prnt) != ok2 )
	    {
	      Error(21, 8, "%s replaced by %s (mark not shared)",
		WARN, &fpos(cover), Image(type(cover)), Image(subst));
	      debug2(DGM, DDD, "  cover = %s %s", Image(type(cover)),
		EchoObject(cover));
	      debug1(DGM, DDD, "  prnt = %s:", Image(type(prnt)));
	      ifdebug(DGM, DDD, DebugObject(prnt));
	      type(cover) = subst;
	    }
	    else if( back(chld, dirn) == 0 && fwd(chld, dirn) == 0 )
	    {
	      /* empty object, this is treated as a no-op */
	      type(cover) = esubst;
	    }
	    else if( back(chld, dirn) == 0 || fwd(chld, dirn) == 0 )
	    { Error(21, 9, "%s replaced by %s (infinite scale factor)",
		WARN, &fpos(cover), Image(type(cover)), Image(subst));
	      type(cover) = subst;
	    }
	    else if( size(prnt, dirn) == 0 )
	    { Error(21, 10, "%s replaced by %s (zero scale factor)",
		WARN, &fpos(cover), Image(type(cover)), Image(subst));
	      type(cover) = subst;
	    }
	    else /* sensible scale factor exists */
	    {
	      /* work out proposed scale factor and sizes for cover */
	      sf1 = (float) back(prnt, dirn) / back(chld, dirn);
	      sf2 = (float) fwd(prnt, dirn)  / fwd(chld, dirn);
	      sf = find_max(sf1, sf2) * SF;
	      b = (back(chld, dirn) * sf) / SF;
	      f = (fwd(chld,  dirn) * sf) / SF;

	      /* check whether new object fits */
	      Constrained(cover, &c, dirn, &why);
	      if( FitsConstraint(b, f, c) )
	      {
		/* it fits, so make cover a SCALE object with this size */
		type(cover) = SCALE;
		if( dirn == COLM )
		{ bc(constraint(cover)) = sf;
		  fc(constraint(cover)) = SF;
		}
		else
		{ bc(constraint(cover)) = SF;
		  fc(constraint(cover)) = sf;
		}
		debug0(DSA, D, "  calling AdjustSize from SizeGalley, case COVER_IND");
		AdjustSize(cover, b, f, dirn);
	      }
	      else
	      { Error(21, 11, "%s replaced by %s (insufficient space)",
		  WARN, &fpos(cover), Image(type(cover)), Image(subst));
		type(cover) = subst;
	      }
	    }
	  }
	  break;


	default:
	  
	  assert1(FALSE, "SizeGalley:", Image(type(z)));
	  break;

      }
    }
    TransferLinks(Down(extras), extras, link);
    assert( Down(extras) == extras && Up(extras) == extras, "SizeG: extras!");
    Dispose(extras);
  }
  
  /* insinuate cross references */
  if( crs != nilobj )
  { 
    debug1(DCR, DD, "SizeGalley insinuating %s", EchoObject(crs));
    TransferLinks(Down(crs), crs, Down(hd));
    DisposeObject(crs);
  }

  /* check that *dest_index was found if it was required, and exit */
  if( target != nilobj && *dest_index == nilobj )
    Error(21, 12, "unexpected absence of %s from the body of %s",
      FATAL, &fpos(hd), SymName(target), SymName(actual(hd)));
  debug3(DGM, D, "SizeGalley returning %s,%s  %s;  hd =",
    EchoLength(back(hd, COLM)), EchoLength(fwd(hd, COLM)),
    EchoConstraint(&constraint(hd)));
  ifdebug(DGM, D, DebugGalley(hd, nilobj, 4));
  sized(hd) = TRUE;

} /* end SizeGalley */
