/*@z17.c:Gap Widths:GetGap()@*************************************************/
/*                                                                           */
/*  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:         z17.c                                                      */
/*  MODULE:       Gap Widths                                                 */
/*  EXTERNS:      GetGap(), MinGap(), ExtraGap(), ActualGap(), EchoGap()     */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"


/*****************************************************************************/
/*                                                                           */
/*  int GetWidth(OBJECT x, STYLE *style)                                     */
/*                                                                           */
/*  Get a width from object x, according to the grammar                      */
/*                                                                           */
/*      <width>  ::=  <unsigned number> <units>                              */
/*      <units>  ::=  c  |  i  |  p  |  m  |  f  |  s  |  v  |  y  |  z      */
/*                                                                           */
/*****************************************************************************/

int GetWidth(OBJECT x, STYLE *style)
{ int res;  float num; 
  FULL_CHAR *str;

  debug2(DGW, D, "GetWidth(%s, %s)", EchoObject(x), EchoStyle(style));

  /* make sure we have a WORD or QWORD argument */
  if( !is_word(type(x)) )
  { Error(17, 11, "width is not a simple word (replacing with 5c)",
      WARN, &fpos(x));
    debug1(DGW, D, "GetWidth failing (x = %s)", EchoObject(x));
    return 5 * CM;
  }
  str = string(x);

  /* ignore initial + or - */
  if( *str == '+' || *str == '-' )
  {
    Error(17, 12, "ignoring initial %c character in width", WARN, &fpos(x), *str);
    str++;
  }

  /* if word is empty, error */
  if( *str == '\0' )
  {
    Error(17, 13, "width is empty (replacing with 5c)", WARN, &fpos(x));
    debug0(DGW, D, "GetWidth failing (null word)");
    return 5 * CM;
  }

  /* read the width */
  if( sscanf((char *) str, "%f", &num) != 1 )
  { Error(17, 14, "width missing from %s (replacing with 5c)", WARN,
      &fpos(x), str);
    debug0(DGW, D, "GetWidth failing (width missing)");
    return 5 * CM;
  }
  while( numericchar(*str) )  str++;

  /* make sure there is a units letter */
  if( *str == '\0' )
  {
    Error(17, 15, "unit missing from width %s (5c substituted)", WARN,
      &fpos(x), string(x));
    res = 5 * CM;
  }

  /* make sure there is nothing after the units letter */
  else if( *(str + 1) != '\0' )
  {
    Error(17, 16, "extra character(s) at end of width %s (5c substituted)",
      WARN, &fpos(x), string(x));
    res = 5 * CM;
  }

  /* read the compulsory unit and calculate length */
  else switch( *str )
  {
    case CH_UNIT_CM:
      
      res = num * CM;
      break;

    case CH_UNIT_IN:
      
      res = num * IN;
      break;

    case CH_UNIT_PT:

      res = num * PT;
      break;

    case CH_UNIT_EM:
      
      res = num * EM;
      break;

    case CH_UNIT_FT:
      
      res = num * FontSize(font(*style), x);
      break;

    case CH_UNIT_SP:
      
      res = num * width(space_gap(*style));
      break;

    case CH_UNIT_VS:
      
      res = num * width(line_gap(*style));
      break;

    case CH_UNIT_YU:
      
      res = num * yunit(*style);
      break;

    case CH_UNIT_ZU:
      
      res = num * zunit(*style);
      break;

    case CH_UNIT_WD:
    case CH_UNIT_BD:
    case CH_UNIT_RL:
    case CH_UNIT_DG:
			
      Error(17, 17, "'%c' unit not allowed in width (5c substituted)",
	WARN, &fpos(x), *str);
      res = 5 * CM;
      break;

    default:
      
      Error(17, 18, "unknown unit in width %s (5c substituted)", WARN,
	&fpos(x), string(x));
      res = 5 * CM;
      break;
  }
  debug1(DGW, D, "GetWidth returning %d", res);
  return res;
}


/*****************************************************************************/
/*                                                                           */
/*  GetGap(x, style, res_gap, res_inc)                                       */
/*                                                                           */
/*  Object x is expected to be a WORD or QWORD containing a gap:             */
/*                                                                           */
/*      <gap>        ::=  [ <increment> ] <width> [ <mode> ] [ <nobreak> ]   */
/*                   ::=                                                     */
/*                                                                           */
/*      <nobreak>    ::=  u                                                  */
/*      <increment>  ::=  +  |  -                                            */
/*      <width>      ::=  <unsigned number> <units>                          */
/*      <units>      ::=  c  |  i  |  p  |  m  |  f  |  s                    */
/*                   ::=  v  |  w  |  b  |  r  |  d  |  y  |  z              */
/*      <mode>       ::=  e  |  h  |  x  |  o  |  k  |  t  |  n              */
/*                                                                           */
/*  Set *res_gap to the gap in the strings of x; *res_inc is the increment.  */
/*  The gap is calculated using the given style.                             */
/*  If the gap is empty, this is a synonym for 0ie.                          */
/*  If there is an error, GetGap prints a message and returns 0ie.           */
/*                                                                           */
/*****************************************************************************/
#define setwidths(x, y) w = x; units(*res_gap) = y;  break;

void GetGap(OBJECT x, STYLE *style, GAP *res_gap, unsigned *res_inc)
{ int w;  float num; 
  FULL_CHAR *str;

  debug2(DGW, D, "GetGap( %s, %s, res_gap, res_inc )",
	EchoObject(x), EchoStyle(style));

  nobreak(*res_gap) = FALSE;
  width(*res_gap) = 0;  units(*res_gap) = FIXED_UNIT;
  mode(*res_gap)  = EDGE_MODE;  *res_inc = GAP_ABS;

  /* make sure we have a WORD or QWORD argument */
  if( !is_word(type(x)) )
  { Error(17, 1, "gap is not a simple word", WARN, &fpos(x));
    debug1(DGW, D, "GetGap failing (x = %s)", EchoObject(x));
    return;
  }
  str = string(x);

  /* if word is empty, return 0ie */
  if( *str == '\0' )
  { debug0(DGW, D, "GetGap returning (null word)");
    return;
  }

  /* read the optional gap increment */
  if( *str == CH_INCGAP )       *res_inc = GAP_INC, str++;
  else if( *str == CH_DECGAP )  *res_inc = GAP_DEC, str++;

  /* read the gap width */
  if( sscanf((char *) str, "%f", &num) != 1 )
  { Error(17, 2, "width missing from %s", WARN, &fpos(x), string(x));
    Error(17, 3, "%s, %s and %s must be enclosed in double quotes",
      WARN, &fpos(x), KW_VCAT_NJ, KW_HCAT_NJ, KW_ACAT_NJ);
    debug0(DGW, D, "GetGap failing (width missing)");
    return;
  }
  while( numericchar(*str) )  str++;

  /* read the compulsory units, calculate length, and check reasonableness */
  switch( *str++ )
  {
    case CH_UNIT_CM:	setwidths( num*CM,                        FIXED_UNIT );
    case CH_UNIT_IN:	setwidths( num*IN,                        FIXED_UNIT );
    case CH_UNIT_PT:	setwidths( num*PT,                        FIXED_UNIT );
    case CH_UNIT_EM:	setwidths( num*EM,                        FIXED_UNIT );
    case CH_UNIT_FT:	setwidths( num*FontSize(font(*style), x), FIXED_UNIT );
    case CH_UNIT_SP:	setwidths( num*width(space_gap(*style)),  FIXED_UNIT );
    case CH_UNIT_VS:	setwidths( num*width(line_gap(*style)),   FIXED_UNIT );
    case CH_UNIT_YU:	setwidths( num*yunit(*style),             FIXED_UNIT );
    case CH_UNIT_ZU:	setwidths( num*zunit(*style),             FIXED_UNIT );
    case CH_UNIT_WD:	setwidths( num*FR,                        NEXT_UNIT  );
    case CH_UNIT_BD:	setwidths( num*FR,                        FRAME_UNIT );
    case CH_UNIT_RL:	setwidths( num*FR,                        AVAIL_UNIT );

    case CH_UNIT_DG:	if( *res_inc == GAP_DEC ) num = - num;
			*res_inc = GAP_ABS;
			while( num >  180.0 ) num -= 360.0;
			while( num < -180.0 ) num += 360.0;
			assert((num>=-180.0) && (num<=180.0), "GetGap: dg!");
			setwidths( num*DG,                        DEG_UNIT   );

    default:	Error(17, 4, "units letter missing from %s",
		  WARN, &fpos(x), string(x));
		debug0(DGW, D, "GetGap failing (units letter missing)");
		return;
  }

  if( units(*res_gap) == AVAIL_UNIT && w > FR )
  { Error(17, 5, "%.1fr too large (1.0r substituted)", WARN, &fpos(x), num);
    w = FR;
  }
  /* don't have short lengths any more ***
  if( w > MAX_SHORT_LENGTH )
    Error(17, 5, "%s exceeds maximum allowed gap size", INTERN, &fpos(x),
      string(x));
  *** */
  width(*res_gap) = w;

  /* read the optional gap mode */
  switch( *str )
  {
    case CH_NOBREAK:
    case '\0':		mode(*res_gap) = EDGE_MODE;          break;
    case CH_MODE_EDGE:	mode(*res_gap) = EDGE_MODE;  str++;  break;
    case CH_MODE_HYPH:	mode(*res_gap) = HYPH_MODE;  str++;  break;
    case CH_MODE_MARK:	mode(*res_gap) = MARK_MODE;  str++;  break;
    case CH_MODE_OVER:	mode(*res_gap) = OVER_MODE;  str++;  break;
    case CH_MODE_KERN:	mode(*res_gap) = KERN_MODE;  str++;  break;
    case CH_MODE_TABL:	mode(*res_gap) = TAB_MODE;   str++;  break;

    default:	Error(17, 7, "unknown gap mode in %s",
		  WARN, &fpos(x), string(x));
		debug0(DGW, D, "GetGap failing (spacing mode)");
		return;
  }

  /* read the optional nobreak */
  if( *str == CH_NOBREAK )
  {
    if( mode(*res_gap) == HYPH_MODE )
      Error(17, 9, "replacing self-contradictory gap %s by breakable version",
	WARN, &fpos(x), string(x));
    else nobreak(*res_gap) = TRUE;
    str++;
  }

  /* if string has not terminated, error */
  if( *str != '\0' )
    Error(17, 8, "invalid width or gap %s", WARN, &fpos(x), string(x));

  debug2(DGW, D, "GetGap returning (res_gap = %s, res_inc = %s)",
    EchoGap(res_gap), Image( (int) *res_inc) );
} /* end GetGap */


/*@::MinGap()@****************************************************************/
/*                                                                           */
/*  FULL_LENGTH MinGap(a, b, c, xgap)                                        */
/*                                                                           */
/*  Returns the minimum possible separation between the marks of two         */
/*  objects with the given intervening gap.                                  */
/*  The first object has fwd value a, the second has back value b and fwd c. */
/*                                                                           */
/*****************************************************************************/

FULL_LENGTH MinGap(FULL_LENGTH a, FULL_LENGTH b, FULL_LENGTH c, GAP *xgap)
{ FULL_LENGTH res;  int w = 0;
  switch( units(*xgap) )
  {
    case FIXED_UNIT:	w = width(*xgap);
			break;

    case FRAME_UNIT:	w = 0;
			break;

    case AVAIL_UNIT:	w = 0;
			break;

    case NEXT_UNIT:	w = width(*xgap) * (b + c) / FR;
			break;

    default:		assert(FALSE, "MinGap: units");
			break;
  }
  switch( mode(*xgap) )
  {
    case NO_MODE:	assert(FALSE, "MinGap: NO_MODE");
			res = 0;
			break;

    case ADD_HYPH:
    case HYPH_MODE:
    case EDGE_MODE:	res = find_min(MAX_FULL_LENGTH, a + w + b);
			break;

    case MARK_MODE:	if( BackEnd->fractional_spacing_avail )
			  res = find_max(w, a + b + (FULL_LENGTH) (0.1 * w) );
			else
			  res = find_max(w, a + b);
			break;

    case OVER_MODE:	res = w;
			break;

    case KERN_MODE:	res = find_max(find_max(a, b), w);
			break;

    case TAB_MODE:	res = a + b;
			break;

    default:		assert(FALSE, "MinGap: mode");
			res = 0;
			break;

  }
  debug5(DGW, DD, "MinGap( _,%s  %s  %s,%s ) = %s", EchoLength(a),
	EchoGap(xgap), EchoLength(b), EchoLength(c), EchoLength(res) );
  return res;
} /* end MinGap */


/*@::ExtraGap()@**************************************************************/
/*                                                                           */
/*  FULL_LENGTH ExtraGap(a, b, xgap, dir)                                    */
/*                                                                           */
/*  Consider two objects, the first with forward length a, the second with   */
/*  back length b.  The objects are separated by the given gap.              */
/*  If dir == FWD, ExtraGap returns the maximum amount that a could be       */
/*  increased without increasing MinGap(a, b, c, xgap).                      */
/*  If dir == BACK, similarly for b.                                         */
/*                                                                           */
/*****************************************************************************/

FULL_LENGTH ExtraGap(FULL_LENGTH a, FULL_LENGTH b, GAP *xgap, int dir)
{ FULL_LENGTH tmp, res;
  FULL_LENGTH w = units(*xgap) == FIXED_UNIT ? width(*xgap) : 0;
  switch( mode(*xgap) )
  {
    case NO_MODE:	assert(FALSE, "ExtraGap: NO_MODE");
			res = 0;
			break;

    case ADD_HYPH:
    case HYPH_MODE:
    case EDGE_MODE:	res = 0;
			break;

    case MARK_MODE:	if( BackEnd->fractional_spacing_avail )
			  res = find_max(0, (FULL_LENGTH) (0.9 * w) - a - b);
			else
			  res = find_max(0, w - a - b);
			break;

    case OVER_MODE:	res = MAX_FULL_LENGTH;
			break;

    case KERN_MODE:	tmp = find_max(a, find_max(b, w));
			res = dir == BACK ? tmp - b : tmp - a;
			break;

    case TAB_MODE:	res = 0;
			break;

    default:		assert(FALSE, "ExtraGap: mode");
			res = 0;
			break;

  }
  debug5(DGW, DD, "ExtraGap( %s, %s, %s, %s ) = %s", EchoLength(a),
		EchoLength(b), EchoGap(xgap), Image(dir), EchoLength(res));
  return res;
} /* end ExtraGap */


/*@::ActualGap()@*************************************************************/
/*                                                                           */
/*  FULL_LENGTH ActualGap(prevf, b, f, xgap, frame_size, mk)                 */
/*                                                                           */
/*  Returns the actual separation between the marks of an object of size     */
/*  (?, prevf) and an object of size (b, f) separated by gap *xgap in a      */
/*  frame of size frame_size; the first object lies at mk in the frame,      */
/*  where 0 <= mk <= frame_size.                                             */
/*                                                                           */
/*****************************************************************************/

FULL_LENGTH ActualGap(FULL_LENGTH prevf, FULL_LENGTH b, FULL_LENGTH f,
  GAP *xgap, FULL_LENGTH frame_size, FULL_LENGTH mk)
{ FULL_LENGTH res;  int w = 0, w2;
  switch( units(*xgap) )
  {
    case FIXED_UNIT:	w = width(*xgap);
			break;

    case FRAME_UNIT:	if( width(*xgap) > FR )
			  w = MAX_FULL_LENGTH;
			else
			  w = (width(*xgap) * frame_size) / FR;
			break;

    case AVAIL_UNIT:	w = (width(*xgap) * (frame_size - b - f)) / FR;
			w = find_max(w, 0);
			break;

    case NEXT_UNIT:	w = width(*xgap) * (b + f) / FR;
			break;

    default:		assert(FALSE, "ActualGap: units");
			break;
  }
  switch( mode(*xgap) )
  {
    case NO_MODE:	Error(17, 10, "cannot continue after previous error(s)", FATAL, no_fpos);
			assert(FALSE, "ActualGap: NO_MODE");
			w2 = 0;
			break;

    case ADD_HYPH:
    case HYPH_MODE:
    case EDGE_MODE:	w2 = prevf + w + b;
			break;

    case MARK_MODE:	if( BackEnd->fractional_spacing_avail )
			  w2 = find_max(w, prevf + b + (FULL_LENGTH) (0.1 * w) );
			else
			  w2 = find_max(w, prevf + b);
			break;

    case OVER_MODE:	w2 = w;
			break;

    case KERN_MODE:	w2 = find_max( find_max(prevf, b), w);
			break;

    case TAB_MODE:	w2 = w + b - mk;
			w2 = find_max(w2, prevf + b );
			break;

    default:		assert(FALSE, "ActualGap: mode");
			w2 = 0;
			break;
  }
  res = find_min(MAX_FULL_LENGTH, w2);
  debug7(DGW, DD, "ActualGap( _,%s %s %s,%s; frame_size %s, mk %s ) = %s",
	EchoLength(prevf), EchoGap(xgap), EchoLength(b), EchoLength(f),
	EchoLength(frame_size), EchoLength(mk), EchoLength(res));
  return res;
} /* end ActualGap */


/*@::EchoGap()@***************************************************************/
/*                                                                           */
/*  FULL_CHAR *EchoGap(xgap)                                                 */
/*                                                                           */
/*  Returns a static string showing the indicated xgap.                      */
/*                                                                           */
/*****************************************************************************/
#if DEBUG_ON

FULL_CHAR *EchoGap(GAP *xgap)
{ char *letter = "?ehxoktH";  char c;  FULL_CHAR *res;
  char *u;
  static int i = 0;
  static char buff[3][20];
  c = mode(*xgap) <= 7 ? letter[mode(*xgap)] : '?';
  c = letter[mode(*xgap)];
  u = nobreak(*xgap) ? "u" : "";
  switch( units(*xgap) )
  {
    case 0:	     sprintf(buff[i], "(none)%c", c);
		     break;

    case FIXED_UNIT: sprintf(buff[i], "%.1fc%c%s", (float) width(*xgap)/CM, c,u);
		     break;

    case NEXT_UNIT:  sprintf(buff[i], "%.1fw%c%s", (float) width(*xgap)/FR, c,u);
		     break;

    case FRAME_UNIT: sprintf(buff[i], "%.1fb%c%s", (float) width(*xgap)/FR, c,u);
		     break;

    case AVAIL_UNIT: sprintf(buff[i], "%.1fr%c%s", (float) width(*xgap)/FR, c,u);
		     break;

    case DEG_UNIT:   sprintf(buff[i], "%.1fd", (float) width(*xgap) / DG);
		     break;

    default:	     assert(FALSE, "EchoGap: units");
		     break;

  }
  res = AsciiToFull(buff[i]);
  i = (i + 1) % 3;
  return res;
} /* end EchoGap */
#endif
