blob: c195dd78fb4282f4f147b970e2e4ff30b0ea3d6d [file] [log] [blame]
/*@z12.c:Size Finder:MinSize()@***********************************************/
/* */
/* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.24) */
/* COPYRIGHT (C) 1991, 2000 Jeffrey H. Kingston */
/* */
/* Jeffrey H. Kingston (jeff@cs.usyd.edu.au) */
/* Basser Department of Computer Science */
/* 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 2, 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: z12.c */
/* MODULE: Size Finder */
/* EXTERNS: MinSize() */
/* */
/*****************************************************************************/
#include "externs.h"
#define line_breaker(g) \
(vspace(g) > 0 || (units(gap(g)) == FRAME_UNIT && width(gap(g)) > FR))
#define IG_LOOKING 0
#define IG_NOFILE 1
#define IG_BADFILE 2
#define IG_BADSIZE 3
#define IG_OK 4
#if DEBUG_ON
static int debug_depth = 1;
static int debug_depth_max = 5;
#endif
/*****************************************************************************/
/* */
/* KernLength(fnum, ch1, ch2, res) */
/* */
/* Set res to the kern length between ch1 and ch2 in font fnum, or 0 if */
/* none. */
/* */
/*****************************************************************************/
static FULL_LENGTH KernLength(FONT_NUM fnum, FULL_CHAR ch1, FULL_CHAR ch2)
{ FULL_LENGTH res;
MAPPING m = font_mapping(finfo[fnum].font_table);
FULL_CHAR *unacc = MapTable[m]->map[MAP_UNACCENTED];
int ua_ch1 = unacc[ch1];
int ua_ch2 = unacc[ch2];
int i = finfo[fnum].kern_table[ua_ch1], j;
if( i == 0 ) res = 0;
else
{ FULL_CHAR *kc = finfo[fnum].kern_chars;
for( j = i; kc[j] > ua_ch2; j++ );
res = (kc[j] == ua_ch2) ?
finfo[fnum].kern_sizes[finfo[fnum].kern_value[j]] : 0;
}
return res;
} /* end KernLength */
/*****************************************************************************/
/* */
/* BuildSpanner(x) */
/* */
/* Build a spanning structure starting at x. */
/* */
/*****************************************************************************/
static BOOLEAN BuildSpanner(OBJECT x)
{ OBJECT link, prnt, y, hspanner, vspanner, end_link, t, hprnt, vprnt, spanobj;
BOOLEAN need_hspanner, need_vspanner;
debug1(DSF, DD, "BuildSpanner(%s)", EchoObject(x));
assert( type(x) == START_HVSPAN || type(x) == START_HSPAN ||
type(x) == START_VSPAN , "BuildSpanner: type(x) != SPAN!" );
Child(spanobj, Down(x));
assert(Up(spanobj) == LastUp(spanobj), "BuildSpanner: spanobj!" );
DeleteLink(Up(spanobj));
need_hspanner = (type(x) == START_HVSPAN || type(x) == START_HSPAN);
if( need_hspanner )
{
/* check that column context is legal, if not exit with FALSE */
Parent(hprnt, UpDim(x, COLM));
if( type(hprnt) != COL_THR )
{
Error(12, 10, "%s deleted (not in column)", WARN,&fpos(x),Image(type(x)));
return FALSE;
}
/* build hspanner object and interpose it between x and spanobj */
New(hspanner, HSPANNER);
FposCopy(fpos(hspanner), fpos(x));
spanner_broken(hspanner) = FALSE;
Link(x, hspanner);
Link(hspanner, spanobj);
/* link later members of the spanner on the same row mark to hspanner */
/* by definition this is every member across to the last @HSpan before a */
/* @StartHVSpan or @StartHSpan or @StartVSpan or @VSpan or end of row */
Parent(prnt, UpDim(x, ROWM));
if( type(prnt) != ROW_THR )
{
Error(12, 11, "%s symbol out of place", FATAL, &fpos(x), Image(type(x)));
}
assert(type(prnt) == ROW_THR, "BuildSpanner: type(prnt)!");
spanner_sized(hspanner) = spanner_fixed(hspanner) = 0;
spanner_count(hspanner) = 1;
end_link = NextDown(UpDim(x, ROWM));
for( link = NextDown(UpDim(x, ROWM)); link != prnt; link = NextDown(link) )
{ Child(y, link);
debug2(DSF, DD, " examining ver %s %s", Image(type(y)), EchoObject(y));
if( type(y) == HSPAN )
end_link = NextDown(link);
else if( type(y) == START_HVSPAN || type(y) == START_HSPAN ||
type(y) == START_VSPAN || type(y) == VSPAN )
break;
}
for( link = NextDown(UpDim(x,ROWM)); link!=end_link; link = NextDown(link) )
{
/* each of these components becomes @HSpan and is added to hspanner */
Child(y, link);
New(t, HSPAN);
FposCopy(fpos(t), fpos(y));
ReplaceNode(t, y);
DisposeObject(y);
Link(t, hspanner);
spanner_count(hspanner)++;
}
}
else Link(x, spanobj);
need_vspanner = (type(x) == START_HVSPAN || type(x) == START_VSPAN);
if( need_vspanner )
{
/* check that row context is legal, if not exit with FALSE */
Parent(vprnt, UpDim(x, ROWM));
if( type(vprnt) != ROW_THR )
{
Error(12, 12, "%s deleted (not in row)", WARN, &fpos(x), Image(type(x)));
return FALSE;
}
/* build vspanner object and interpose it between x and spanobj */
New(vspanner, VSPANNER);
FposCopy(fpos(vspanner), fpos(x));
spanner_broken(vspanner) = FALSE;
Link(x, vspanner);
Link(vspanner, spanobj);
/* link later members of the spanner on the same column mark to vspanner */
/* by definition this is every member down to the last @VSpan before a */
/* @StartHVSpan or @StartHSpan or @StartVSpan or @HSpan or end of column */
Parent(prnt, UpDim(x, COLM));
assert(type(prnt) == COL_THR, "BuildSpanner: type(prnt)!");
spanner_sized(vspanner) = spanner_fixed(vspanner) = 0;
spanner_count(vspanner) = 1;
end_link = NextDown(UpDim(x, COLM));
for( link = NextDown(UpDim(x, COLM)); link != prnt; link = NextDown(link) )
{ Child(y, link);
debug2(DSF, DD, " examining hor %s %s", Image(type(y)), y);
if( type(y) == VSPAN )
end_link = NextDown(link);
else if( type(y) == START_HVSPAN || type(y) == START_HSPAN ||
type(y) == START_VSPAN || type(y) == HSPAN )
break;
}
for( link = NextDown(UpDim(x,COLM)); link!=end_link; link = NextDown(link) )
{
/* each of these components becomes @VSpan and is added to vspanner */
Child(y, link);
New(t, VSPAN);
FposCopy(fpos(t), fpos(y));
ReplaceNode(t, y);
DisposeObject(y);
Link(t, vspanner);
spanner_count(vspanner)++;
}
}
else Link(x, spanobj);
debug2(DSF, DD, "BuildSpanner returning TRUE (rows = %d, cols = %d)",
need_vspanner ? spanner_count(vspanner) : 0,
need_hspanner ? spanner_count(hspanner) : 0);
ifdebug(DSF, DD, DebugObject(x));
return TRUE;
}
/*****************************************************************************/
/* */
/* BOOLEAN FindSpannerGap(thr, cat_op, gp) */
/* */
/* For the purposes of calculating spanning spacing, find the gap between */
/* this object and the preceding one under the nearest cat_op. */
/* */
/* If found, set &gp to the gap object and return TRUE; else return FALSE. */
/* */
/*****************************************************************************/
static BOOLEAN FindSpannerGap(OBJECT thr, unsigned dim, unsigned cat_op,
OBJECT *res)
{ OBJECT link, x;
/* find nearest enclosing cat_op that we aren't the first element of */
link = UpDim(thr, dim);
Parent(x, link);
while( (type(x) != cat_op || type(PrevDown(link)) != LINK) && Up(x) != x )
{ link = UpDim(x, dim);
Parent(x, link);
}
/* if found and a gap precedes thr's component of it, return that gap */
if( type(x) == cat_op && type(PrevDown(link)) == LINK )
{ Child(*res, PrevDown(link));
assert(type(*res) == GAP_OBJ, "FindSpannerGap: type(*res)!" );
}
else if( type(x) == HEAD && gall_dir(x)==dim && type(PrevDown(link))==LINK )
{ Child(*res, PrevDown(link));
assert(type(*res) == GAP_OBJ, "FindSpannerGap (HEAD): type(*res)!" );
nobreak(gap(*res)) = TRUE;
}
else *res = nilobj;
debug1(DSF, DD, " FindSpannerGap returning %s", EchoObject(*res));
return (*res != nilobj);
}
/*****************************************************************************/
/* */
/* void SpannerAvailableSpace(x, dim, rb, rf) */
/* */
/* Work out the total space available to hold this spanner, and set */
/* (*rb, *rf) to record this value. This space equals the total width */
/* of all columns (and their intervening gaps) spanned, with the mark */
/* of the last column being the one separating rb from rf. */
/* */
/*****************************************************************************/
void SpannerAvailableSpace(OBJECT y, int dim, FULL_LENGTH *resb,
FULL_LENGTH *resf)
{ OBJECT slink, s, thr, gp, prevthr;
FULL_LENGTH b, f;
unsigned thr_type, cat_type;
assert( type(y) == HSPANNER || type(y) == VSPANNER, "SpannerAvail!");
debug4(DSF, DD, "SpannerAvailableSpace(%d %s %s, %s)",
spanner_count(y), Image(type(y)), EchoObject(y), dimen(dim));
if( dim == COLM )
{ thr_type = COL_THR;
cat_type = HCAT;
}
else
{ thr_type = ROW_THR;
cat_type = VCAT;
}
/* first calculate the total space consumed in earlier spans */
/* Invariant: (b, f) is the size up to and including prev */
/* */
prevthr = nilobj;
for( slink = Up(y); slink != y; slink = NextUp(slink) )
{ Parent(s, slink);
Parent(thr, UpDim(s, dim));
if( type(thr) == thr_type )
{
assert( thr_state(thr) == SIZED, "SpannerAvailableSpace: thr_state!" );
if( prevthr == nilobj )
{
/* this is the first column spanned over */
b = back(thr, dim);
f = fwd(thr, dim);
debug4(DSF, DD, " first component %s,%s: b = %s, f = %s",
EchoLength(back(thr, dim)), EchoLength(fwd(thr, dim)),
EchoLength(b), EchoLength(f));
}
else if( FindSpannerGap(thr, dim, cat_type, &gp) )
{
/* this is a subquent column spanned over */
b += MinGap(fwd(prevthr, dim), back(thr, dim), fwd(thr, dim), &gap(gp));
f = fwd(thr, dim);
debug5(DSF, DD, " later component %s,%s: gp = %s, b = %s, f = %s",
EchoLength(back(thr, dim)), EchoLength(fwd(thr, dim)), EchoObject(gp),
EchoLength(b), EchoLength(f));
}
else
{
Error(12, 13, "search for gap preceding %s failed, using zero",
WARN, &fpos(s), Image(type(s)));
b += fwd(prevthr, dim) + back(thr, dim);
f = fwd(thr, dim);
debug4(DSF, DD, " later component %s,%s: (no gap), b = %s, f = %s",
EchoLength(back(thr, dim)), EchoLength(fwd(thr, dim)),
EchoLength(b), EchoLength(f));
}
}
else
Error(12, 14, "%s deleted (out of place)", WARN,&fpos(s),Image(type(s)));
prevthr = thr;
}
*resb = b;
*resf = f;
SetConstraint(constraint(y), MAX_FULL_LENGTH, b+f, MAX_FULL_LENGTH);
debug2(DSF, DD, "SpannerAvailableSpace returning %s,%s",
EchoLength(*resb), EchoLength(*resf));
} /* end SpannerAvailableSpace */
/*****************************************************************************/
/* */
/* OBJECT MinSize(x, dim, extras) */
/* */
/* Set fwd(x, dim) and back(x, dim) to their minimum possible values. */
/* If dim == ROWM, construct an extras list and return it in *extras. */
/* */
/*****************************************************************************/
OBJECT MinSize(OBJECT x, int dim, OBJECT *extras)
{ OBJECT y, z, link, prev, t, g, full_name;
FULL_LENGTH b, f, dble_fwd, llx, lly, urx, ury; int status, read_status;
float fllx, flly, furx, fury;
BOOLEAN dble_found, found, will_expand, first_line, cp;
FILE *fp; FULL_CHAR buff[MAX_BUFF];
debug2(DSF, DD, "[ MinSize( %s, %s, extras )", EchoObject(x), dimen(dim));
debugcond4(DSF, D, dim == COLM && debug_depth++ < debug_depth_max,
"%*s[ MinSize(COLM, %s %d)", (debug_depth-1)*2, " ",
Image(type(x)), (int) x);
ifdebug(DSF, DDD, DebugObject(x));
switch( type(x) )
{
case WORD:
case QWORD:
if( dim == COLM ) FontWordSize(x);
break;
case CROSS:
case FORCE_CROSS:
/* add index to the cross-ref */
if( dim == ROWM )
{ New(z, cross_type(x)); /* CROSS_PREC, CROSS_FOLL or CROSS_FOLL_OR_PREC */
debug2(DCR, DD, " MinSize CROSS: %ld %s", (long) x, EchoObject(x));
actual(z) = x;
Link(z, x); /* new code to avoid disposal */
Link(*extras, z);
debug2(DCR, DD, " MinSize index: %ld %s", (long) z, EchoObject(z));
}
back(x, dim) = fwd(x, dim) = 0;
break;
case PAGE_LABEL:
if( dim == ROWM )
{ New(z, PAGE_LABEL_IND);
actual(z) = x;
Link(z, x);
Link(*extras, z);
}
back(x, dim) = fwd(x, dim) = 0;
break;
case NULL_CLOS:
back(x, dim) = fwd(x, dim) = 0;
break;
case HEAD:
if( dim == ROWM )
{
/* replace the galley x by a dummy closure y */
New(y, NULL_CLOS);
FposCopy(fpos(y), fpos(x));
ReplaceNode(y, x);
if( has_key(actual(x)) )
{
/* galley is sorted, make insinuated cross-reference */
New(z, foll_or_prec(x));
pinpoint(z) = y;
Child(t, Down(x));
actual(z) = CrossMake(whereto(x), t, (int) type(z));
Link(*extras, z);
DisposeObject(x);
debug1(DCR, DDD, " MinSize: %s", EchoObject(z));
}
else
{
/* galley is following, make UNATTACHED */
New(z, UNATTACHED); Link(z, x);
pinpoint(z) = y;
Link(*extras, z);
debug1(DCR, DDD, " MinSize: %s", EchoObject(z));
}
x = y; /* now sizing y, not x */
back(x, COLM) = fwd(x, COLM) = 0; /* fix non-zero size @Null bug!! */
}
else
{
debug2(DGT, DD, "MinSize setting external_ver(%s %s) = FALSE",
Image(type(x)), SymName(actual(x)));
external_ver(x) = external_hor(x) = FALSE;
}
back(x, dim) = fwd(x, dim) = 0;
break;
case CLOSURE:
assert( !has_target(actual(x)), "MinSize: CLOSURE has target!" );
if( dim == ROWM )
{ if( indefinite(actual(x)) )
{ New(z, RECEPTIVE);
actual(z) = x;
Link(*extras, z);
debug1(DCR, DDD, " MinSize: %s", EchoObject(z));
}
else if( recursive(actual(x)) )
{ New(z, RECURSIVE);
actual(z) = x;
Link(*extras, z);
debug1(DCR, DDD, " MinSize: %s", EchoObject(z));
}
else
{ assert(FALSE, "MinSize: definite non-recursive closure");
}
}
else
{
debug2(DGT, DD, "MinSize setting external_ver(%s %s) = FALSE",
Image(type(x)), SymName(actual(x)));
external_ver(x) = external_hor(x) = FALSE;/* nb must be done here!*/
}
back(x, dim) = fwd(x, dim) = 0;
break;
case ONE_COL:
case ONE_ROW:
case HCONTRACT:
case VCONTRACT:
case HLIMITED:
case VLIMITED:
Child(y, Down(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
break;
case BACKGROUND:
Child(y, Down(x));
y = MinSize(y, dim, extras);
Child(y, LastDown(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
break;
case START_HVSPAN:
case START_HSPAN:
case START_VSPAN:
case HSPAN:
case VSPAN:
/* if first touch, build the spanner */
if( (type(x) == START_HVSPAN || type(x) == START_HSPAN ||
type(x) == START_VSPAN) && dim == COLM )
{
if( !BuildSpanner(x) )
{
t = MakeWord(WORD, STR_EMPTY, &fpos(x));
ReplaceNode(t, x);
x = t;
back(x, COLM) = fwd(x, COLM) = 0;
break;
}
}
/* if first vertical touch, break if necessary */
if( (type(x) == START_HVSPAN || type(x) == START_HSPAN) && dim == ROWM )
{ CONSTRAINT c;
/* find the HSPANNER */
Child(t, DownDim(x, COLM));
assert( type(t) == HSPANNER, "MinSize/SPAN: type(t) != HSPANNER!" );
/* find the available space for this HSPANNER and break it */
SpannerAvailableSpace(t, COLM, &b, &f);
SetConstraint(c, MAX_FULL_LENGTH, b+f, MAX_FULL_LENGTH);
debug2(DSF,DD, " BreakObject(%s,%s)",EchoObject(t),EchoConstraint(&c));
t = BreakObject(t, &c);
spanner_broken(t) = TRUE;
}
/* make sure that HSPAN links to HSPANNER, VSPAN to VSPANNER */
/* NB must follow breaking since that could affect the value of y */
Child(y, DownDim(x, dim));
if( (type(x) == HSPAN && type(y) != HSPANNER) ||
(type(x) == VSPAN && type(y) != VSPANNER) )
{
if( dim == COLM )
Error(12, 15, "%s replaced by empty object (out of place)",
WARN, &fpos(x), Image(type(x)));
back(x, dim) = fwd(x, dim) = 0;
break;
}
/* now size the object */
if( (type(x)==HSPAN && dim==ROWM) || (type(x)==VSPAN && dim==COLM) )
{
/* perp dimension, covered by preceding @Span, so may be zero. */
back(x, dim) = fwd(x, dim) = 0;
}
else if( type(y) != HSPANNER && type(y) != VSPANNER )
{
/* no spanning in this dimension */
MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
else if( ++spanner_sized(y) != spanner_count(y) )
{
/* not the last column or row, so say zero */
back(x, dim) = fwd(x, dim) = 0;
}
else
{
/* this is the last column or row of a spanner. Its width is its */
/* natural width minus anything that will fit over the top of the */
/* things it spans. */
MinSize(y, dim, extras);
SpannerAvailableSpace(y, dim, &b, &f);
back(x, dim) = 0;
fwd(x, dim) = find_max(size(y, dim) - b, 0);
debug3(DSF, DD, " size(y, %s) = %s,%s", dimen(dim),
EchoLength(back(y, dim)), EchoLength(fwd(y, dim)));
}
debug4(DSF, DD, "finishing MinSize(%s) of %s span, reporting %s,%s",
dimen(dim), spanner_count(y) != 1 ? "multi-column" : "one-column",
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
break;
case HSPANNER:
case VSPANNER:
assert( (type(x) == HSPANNER) == (dim == COLM), "MinSize: SPANNER!" );
Child(y, Down(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
break;
case HEXPAND:
case VEXPAND:
Child(y, Down(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
/* insert index into *extras for expanding later */
if( dim == ROWM )
{ New(z, EXPAND_IND);
actual(z) = x;
Link(*extras, z);
/* Can't do Link(z, x) because Constrained goes up and finds z */
debug2(DCR, DD, " MinSize index: %ld %s", (long) z, EchoObject(z));
}
break;
case END_HEADER:
case CLEAR_HEADER:
back(x, dim) = fwd(x, dim) = 0;
Child(y, Down(x));
back(y, dim) = fwd(y, dim) = 0;
break;
case BEGIN_HEADER:
case SET_HEADER:
Child(y, LastDown(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
debug4(DSF, D, "MinSize(%s, %s) := (%s, %s)", Image(type(x)),
dimen(dim), EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
break;
case PLAIN_GRAPHIC:
case GRAPHIC:
case LINK_SOURCE:
case LINK_DEST:
Child(y, LastDown(x));
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
break;
case HCOVER:
case VCOVER:
/* work out size and set to 0 if parallel */
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( (dim == COLM) == (type(x) == HCOVER) )
back(x, dim) = fwd(x, dim) = 0;
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
/* insert index into *extras for revising size later */
if( dim == ROWM )
{ New(z, COVER_IND);
actual(z) = x;
Link(*extras, z);
/* Can't do Link(z, x) because Constrained goes up and finds z */
debug2(DCR, DD, " MinSize index: %ld %s", (long) z, EchoObject(z));
}
break;
case HSCALE:
case VSCALE:
/* work out size and set to 0 if parallel */
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( (dim == COLM) == (type(x) == HSCALE) )
back(x, dim) = fwd(x, dim) = 0;
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
break;
case ROTATE:
Child(y, Down(x));
if( dim == COLM )
{ y = MinSize(y, COLM, extras);
New(whereto(x), ACAT);
y = MinSize(y, ROWM, &whereto(x));
RotateSize(&back(x, COLM), &fwd(x, COLM), &back(x, ROWM), &fwd(x, ROWM),
y, sparec(constraint(x)));
}
else
{ TransferLinks(Down(whereto(x)), whereto(x), *extras);
Dispose(whereto(x));
}
break;
case SCALE:
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( dim == COLM )
{ back(x, dim) = (back(y, dim) * bc(constraint(x))) / SF;
fwd(x, dim) = (fwd(y, dim) * bc(constraint(x))) / SF;
if( bc(constraint(x)) == 0 ) /* Lout-supplied factor required */
{ New(z, SCALE_IND);
actual(z) = x;
Link(*extras, z);
debug1(DSF, DDD, " MinSize: %s", EchoObject(z));
vert_sized(x) = FALSE;
}
}
else
{ back(x, dim) = (back(y, dim) * fc(constraint(x))) / SF;
fwd(x, dim) = (fwd(y, dim) * fc(constraint(x))) / SF;
vert_sized(x) = TRUE;
}
break;
case KERN_SHRINK:
Child(y, LastDown(x));
y = MinSize(y, dim, extras);
if( dim == COLM )
{ FULL_CHAR ch_left, ch_right; FULL_LENGTH ksize;
debug3(DSF, DD, "MinSize(%s,%s %s, COLM)",
EchoLength(back(y, COLM)), EchoLength(fwd(y, COLM)),
EchoObject(x));
/* default value if don't find anything */
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
/* find first character of left parameter */
ch_right = (FULL_CHAR) '\0';
Child(y, Down(x));
while( type(y) == ACAT )
{ Child(y, Down(y));
}
if( is_word(type(y)) ) ch_right = string(y)[0];
/* find last character of right parameter */
ch_left = (FULL_CHAR) '\0';
Child(y, LastDown(x));
while( type(y) == ACAT )
{ Child(y, LastDown(y));
}
if( is_word(type(y)) ) ch_left = string(y)[StringLength(string(y))-1];
/* adjust if successful */
if( ch_left != (FULL_CHAR) '\0' && ch_right != (FULL_CHAR) '\0' )
{
ksize = KernLength(word_font(y), ch_left, ch_right);
debug4(DSF, DD, " KernLength(%s, %c, %c) = %s",
FontName(word_font(y)), (char) ch_left, (char) ch_right,
EchoLength(ksize));
fwd(x, dim) += ksize;
}
}
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
break;
case WIDE:
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( dim == COLM )
{ y = BreakObject(y, &constraint(x));
assert( FitsConstraint(back(y, dim), fwd(y, dim), constraint(x)),
"MinSize: BreakObject failed to fit!" );
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
EnlargeToConstraint(&back(x, dim), &fwd(x, dim), &constraint(x));
}
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
break;
case HIGH:
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( dim == ROWM )
{ if( !FitsConstraint(back(y, dim), fwd(y, dim), constraint(x)) )
{ Error(12, 1, "forced to enlarge %s from %s to %s", WARN, &fpos(x),
KW_HIGH, EchoLength(bfc(constraint(x))), EchoLength(size(y, dim)));
debug0(DSF, DD, "offending object was:");
ifdebug(DSF, DD, DebugObject(y));
SetConstraint(constraint(x), MAX_FULL_LENGTH, size(y, dim), MAX_FULL_LENGTH);
}
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
EnlargeToConstraint(&back(x, dim), &fwd(x, dim), &constraint(x));
}
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
break;
case HSHIFT:
case VSHIFT:
Child(y, Down(x));
y = MinSize(y, dim, extras);
if( (dim == COLM) == (type(x) == HSHIFT) )
{ f = FindShift(x, y, dim);
back(x, dim) = find_min(MAX_FULL_LENGTH, find_max(0, back(y, dim) + f));
fwd(x, dim) = find_min(MAX_FULL_LENGTH, find_max(0, fwd(y, dim) - f));
}
else
{ back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
}
break;
case SPLIT:
link = DownDim(x, dim); Child(y, link);
y = MinSize(y, dim, extras);
back(x, dim) = back(y, dim);
fwd(x, dim) = fwd(y, dim);
break;
case ACAT:
if( fill_style(save_style(x)) == FILL_OFF )
{ OBJECT new_line, g, z, res; BOOLEAN jn;
/* convert ACAT to VCAT of lines if more than one line */
/* first, compress all ACAT children */
for( link = x; NextDown(link) != x; link = NextDown(link) )
{ Child(y, NextDown(link));
if( type(y) == ACAT )
{
TransferLinks(Down(y), y, NextDown(link));
DisposeChild(Up(y));
link = PrevDown(link);
}
}
/* check each definite subobject in turn for a linebreak preceding */
FirstDefinite(x, link, y, jn);
if( link != x )
{
res = nilobj;
NextDefiniteWithGap(x, link, y, g, jn);
while( link != x )
{
/* check whether we need to break the paragraph here at g */
if( mode(gap(g)) != NO_MODE && line_breaker(g) )
{
/* if this is our first break, build res */
if( res == nilobj )
{
New(res, VCAT);
adjust_cat(res) = FALSE;
ReplaceNode(res, x);
}
/* make new line of stuff up to g and append it to res */
New(new_line, ACAT);
TransferLinks(NextDown(x), Up(g), new_line);
StyleCopy(save_style(new_line), save_style(x));
adjust_cat(new_line) = padjust(save_style(x));
Link(res, new_line);
debug2(DSF, D, " new_line(adjust_cat %s) = %s",
bool(adjust_cat(new_line)), EchoObject(new_line));
/* may need to insert space at start of remainder */
if( hspace(g) > 0 )
{
/* make an empty word to occupy the first spot */
z = MakeWord(WORD, STR_EMPTY, &fpos(g));
word_font(z) = font(save_style(x));
word_colour(z) = colour(save_style(x));
word_outline(z) = outline(save_style(x));
word_language(z) = language(save_style(x));
word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON;
underline(z) = UNDER_OFF;
back(z, COLM) = fwd(z, COLM) = 0;
Link(Down(x), z);
/* follow the empty word with a gap of the right width */
New(z, GAP_OBJ);
hspace(z) = hspace(g);
vspace(z) = 0;
underline(z) = UNDER_OFF;
GapCopy(gap(z), space_gap(save_style(x)));
width(gap(z)) *= hspace(z);
Link(NextDown(Down(x)), z);
debug2(DSF, D, " hspace(g) = %d, width(gap(z)) = %s",
hspace(g), EchoLength(width(gap(z))));
}
/* append a gap to res (recycle g) */
MoveLink(Up(g), res, PARENT);
GapCopy(gap(g), line_gap(save_style(x)));
width(gap(g)) *= find_max(1, vspace(g));
}
NextDefiniteWithGap(x, link, y, g, jn);
}
/* at end of loop, if we have a res, leftover last line is linked */
if( res != nilobj )
{
Link(res, x);
x = res;
}
}
}
/* *** NB NO BREAK *** */
case HCAT:
case VCAT:
if( (dim == ROWM) == (type(x) == VCAT) )
{
/********************************************************************/
/* */
/* Calculate sizes parallel to join direction; loop invariant is: */
/* */
/* If prev == nilobj, there are no definite children equal to */
/* or to the left of Child(link). */
/* If prev != nilobj, prev is the rightmost definite child to */
/* the left of Child(link), and (b, f) is the total size up */
/* to the mark of prev i.e. not including fwd(prev). */
/* g is the most recent gap, or nilobj if none found yet. */
/* will_expand == TRUE when a gap is found that is likely to */
/* enlarge when ActualGap is called later on. */
/* */
/********************************************************************/
prev = g = nilobj; will_expand = FALSE; must_expand(x) = FALSE;
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
if( is_index(type(y)) )
{ if( dim == ROWM )
{ link = PrevDown(link);
MoveLink(NextDown(link), *extras, PARENT);
}
continue;
}
else if( type(y) == type(x) )
{ link = PrevDown(link);
TransferLinks(Down(y), y, NextDown(link));
DisposeChild(Up(y));
continue;
}
else if( type(y) == GAP_OBJ ) g = y;
else /* calculate size of y and accumulate it */
{ if( is_word(type(y)) )
{ if( dim == COLM )
{
/* compress adjacent words if compatible */
if( prev != nilobj && width(gap(g)) == 0 && nobreak(gap(g)) &&
type(x) == ACAT &&
is_word(type(prev)) && vspace(g) + hspace(g) == 0 &&
units(gap(g)) == FIXED_UNIT &&
mode(gap(g)) == EDGE_MODE && !mark(gap(g)) &&
word_font(prev) == word_font(y) &&
word_colour(prev) == word_colour(y) &&
word_outline(prev) == word_outline(y) &&
word_language(prev) == word_language(y) &&
underline(prev) == underline(y) &&
NextDown(NextDown(Up(prev))) == link
)
{
unsigned typ;
debug3(DSF, DD, "compressing %s and %s at %s",
EchoObject(prev), EchoObject(y), EchoFilePos(&fpos(prev)));
if( StringLength(string(prev)) + StringLength(string(y))
>= MAX_BUFF )
Error(12, 2, "word %s%s is too long", FATAL, &fpos(prev),
string(prev), string(y));
typ = type(prev) == QWORD || type(y) == QWORD ? QWORD : WORD;
y = MakeWordTwo(typ, string(prev), string(y), &fpos(prev));
word_font(y) = word_font(prev);
word_colour(y) = word_colour(prev);
word_outline(y) = word_outline(prev);
word_language(y) = word_language(prev);
word_hyph(y) = word_hyph(prev);
underline(y) = underline(prev);
FontWordSize(y);
Link(Up(prev), y);
DisposeChild(Up(prev));
DisposeChild(Up(g));
DisposeChild(link);
prev = y;
link = Up(prev);
continue;
}
FontWordSize(y);
debug4(DSF, DD, "FontWordSize( %s ) font %d = %s,%s",
EchoObject(y), word_font(y),
EchoLength(back(y, COLM)), EchoLength(fwd(y, COLM)));
}
}
else y = MinSize(y, dim, extras);
if( is_indefinite(type(y)) )
{
/* error if preceding gap has mark */
if( g != nilobj && mark(gap(g)) )
{ Error(12, 3, "^ deleted (it may not precede this object)",
WARN, &fpos(y));
mark(gap(g)) = FALSE;
}
/* error if next unit is used in preceding gap */
if( g != nilobj && units(gap(g)) == NEXT_UNIT )
{ Error(12, 4, "gap replaced by 0i (%c unit not allowed here)",
WARN, &fpos(y), CH_UNIT_WD);
units(gap(g)) = FIXED_UNIT;
width(gap(g)) = 0;
}
}
else
{
/* calculate running total length */
if( prev == nilobj ) b = back(y, dim), f = 0;
else
{ FULL_LENGTH tmp;
tmp = MinGap(fwd(prev,dim), back(y,dim), fwd(y, dim), &gap(g));
assert(g!=nilobj && mode(gap(g))!=NO_MODE, "MinSize: NO_MODE!");
if( units(gap(g)) == FIXED_UNIT && mode(gap(g)) == TAB_MODE )
{
f = find_max(width(gap(g)) + back(y, dim), f + tmp);
}
else
{
f = f + tmp;
}
if( units(gap(g)) == FRAME_UNIT && width(gap(g)) > FR )
will_expand = TRUE;
if( units(gap(g)) == AVAIL_UNIT && mark(gap(g)) && width(gap(g)) > 0 )
Error(12, 9, "mark alignment incompatible with centring or right justification",
WARN, &fpos(g));
/* ***
if( units(gap(g)) == AVAIL_UNIT && width(gap(g)) >= FR )
will_expand = TRUE;
*** */
if( mark(gap(g)) ) b += f, f = 0;
}
prev = y;
}
debug2(DSF,DD," b = %s, f = %s",EchoLength(b),EchoLength(f));
}
} /* end for */
if( prev == nilobj ) b = f = 0;
else f += fwd(prev, dim);
back(x, dim) = find_min(MAX_FULL_LENGTH, b);
fwd(x, dim) = find_min(MAX_FULL_LENGTH, f);
if( type(x) == ACAT && will_expand ) fwd(x, COLM) = MAX_FULL_LENGTH;
}
else
{
/********************************************************************/
/* */
/* Calculate sizes perpendicular to join direction */
/* */
/* Loop invariant: */
/* */
/* if found, (b, f) is the size of x, from the last // or from */
/* the start, up to link exclusive. Else no children yet. */
/* If dble_found, a previous // exists, and (0, dble_fwd) is */
/* the size of x from the start up to that //. */
/* */
/********************************************************************/
dble_found = found = FALSE; dble_fwd = 0;
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
debug4(DSF, DD, " %s in %s, y = %s %s", dimen(dim),
Image(type(x)), Image(type(y)), EchoObject(y));
if( is_index(type(y)) )
{ if( dim == ROWM )
{ link = PrevDown(link);
MoveLink(NextDown(link), *extras, PARENT);
}
continue;
}
else if( type(y) == type(x) )
{ link = PrevDown(link);
TransferLinks(Down(y), y, NextDown(link));
DisposeChild(Up(y));
continue;
}
else if( type(y) == GAP_OBJ )
{ assert( found, "MinSize/VCAT/perp: !found!" );
if( !join(gap(y)) )
{
/* found // or || operator, so end current group */
dble_found = TRUE;
dble_fwd = find_max(dble_fwd, b + f);
debug1(DSF, DD, " endgroup, dble_fwd: %s", EchoLength(dble_fwd));
found = FALSE;
}
}
else /* found object */
{
/* calculate size of subobject y */
if( is_word(type(y)) )
{ if( dim == COLM ) FontWordSize(y);
}
else y = MinSize(y, dim, extras);
if( found )
{ b = find_max(b, back(y, dim));
f = find_max(f, fwd(y, dim));
}
else
{ b = back(y, dim);
f = fwd(y, dim);
found = TRUE;
}
debug2(DSF,DD, " b: %s, f: %s", EchoLength(b), EchoLength(f));
}
} /* end for */
assert( found, "MinSize/VCAT/perp: !found (2)!" );
/* finish off last group */
if( dble_found )
{ back(x, dim) = 0;
dble_fwd = find_max(dble_fwd, b + f);
fwd(x, dim) = find_min(MAX_FULL_LENGTH, dble_fwd);
debug1(DSF, DD, " end group, dble_fwd: %s", EchoLength(dble_fwd));
}
else
{ back(x, dim) = b;
fwd(x, dim) = f;
}
} /* end else */
break;
case COL_THR:
assert( dim == COLM, "MinSize/COL_THR: dim!" );
if( thr_state(x) == NOTSIZED )
{ assert( Down(x) != x, "MinSize/COL_THR: Down(x)!" );
/* first size all the non-spanning members of the thread */
debug1(DSF, DD, "[[ starting sizing %s", Image(type(x)));
b = f = 0;
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
assert( type(y) != GAP_OBJ, "MinSize/COL_THR: GAP_OBJ!" );
if( type(y) != START_HVSPAN && type(y) != START_HSPAN &&
type(y) != HSPAN && type(y) != VSPAN )
{ y = MinSize(y, dim, extras);
b = find_max(b, back(y, dim));
f = find_max(f, fwd(y, dim));
}
}
back(x, dim) = b;
fwd(x, dim) = f;
thr_state(x) = SIZED;
debug3(DSF, DD, "][ middle sizing %s (%s,%s)", Image(type(x)),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
/* now size all the spanning members of the thread */
/* these will use back(x, dim) and fwd(x, dim) during sizing */
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
assert( type(y) != GAP_OBJ, "MinSize/COL_THR: GAP_OBJ!" );
if( type(y) == START_HVSPAN || type(y) == START_HSPAN ||
type(y) == HSPAN || type(y) == VSPAN )
{ y = MinSize(y, dim, extras);
b = find_max(b, back(y, dim));
f = find_max(f, fwd(y, dim));
}
}
back(x, dim) = b;
fwd(x, dim) = f;
debug3(DSF, DD, "]] end sizing %s (%s,%s)", Image(type(x)),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
}
break;
case ROW_THR:
assert( dim == ROWM, "MinSize/ROW_THR: dim!" );
if( thr_state(x) == NOTSIZED )
{ assert( Down(x) != x, "MinSize/ROW_THR: Down(x)!" );
/* first size all the non-spanning members of the thread */
debug1(DSF, DD, "[[ starting sizing %s", Image(type(x)));
b = f = 0;
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
assert( type(y) != GAP_OBJ, "MinSize/ROW_THR: GAP_OBJ!" );
if( type(y) != START_HVSPAN && type(y) != START_VSPAN &&
type(y) != HSPAN && type(y) != VSPAN )
{ y = MinSize(y, dim, extras);
debug5(DSF, DD, " MinSize(%s) has size %s,%s -> %s,%s",
Image(type(y)), EchoLength(back(y, dim)), EchoLength(fwd(y, dim)),
EchoLength(b), EchoLength(f));
b = find_max(b, back(y, dim));
f = find_max(f, fwd(y, dim));
}
}
back(x, dim) = b;
fwd(x, dim) = f;
thr_state(x) = SIZED;
debug3(DSF, DD, "][ middle sizing %s (%s,%s)", Image(type(x)),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
/* now size all the spanning members of the thread */
/* these will use back(x, dim) and fwd(x, dim) during sizing */
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
assert( type(y) != GAP_OBJ, "MinSize/ROW_THR: GAP_OBJ!" );
if( type(y) == START_HVSPAN || type(y) == START_VSPAN ||
type(y) == HSPAN || type(y) == VSPAN )
{ y = MinSize(y, dim, extras);
back(x, dim) = find_max(back(x, dim), back(y, dim));
fwd(x, dim) = find_max(fwd(x, dim), fwd(y, dim));
debug5(DSF, DD, " MinSize(%s) has size %s,%s -> %s,%s",
Image(type(y)), EchoLength(back(y, dim)), EchoLength(fwd(y, dim)),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
}
}
debug3(DSF, DD, "]] end sizing %s (%s,%s)", Image(type(x)),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
}
break;
case INCGRAPHIC:
case SINCGRAPHIC:
/* open file, check for initial %!, and hunt for %%BoundingBox line */
/* according to DSC Version 3.0, the BoundingBox parameters must be */
/* integers; but we read them as floats and truncate since files */
/* with fractional values seem to be common in the real world */
if( dim == ROWM ) break;
status = IG_LOOKING;
Child(y, Down(x));
fp = OpenIncGraphicFile(string(y), type(x), &full_name, &fpos(y), &cp);
if( fp == NULL ) status = IG_NOFILE;
first_line = TRUE;
/* ***
while( status == IG_LOOKING && StringFGets(buff, MAX_BUFF, fp) != NULL )
*** */
while( status == IG_LOOKING )
{
read_status = fscanf(fp, "%[^\n\r]%*c", (char *) buff);
if( read_status == 0 || read_status == EOF )
{
/* end of input and no luck */
break;
}
if( first_line && !StringBeginsWith(buff, AsciiToFull("%!")) )
status = IG_BADFILE;
else
{ first_line = FALSE;
if( buff[0] == '%'
&& StringBeginsWith(buff, AsciiToFull("%%BoundingBox:"))
&& !StringContains(buff, AsciiToFull("(atend)")) )
{ if( sscanf( (char *) buff, "%%%%BoundingBox: %f %f %f %f",
&fllx, &flly, &furx, &fury) == 4 )
{
status = IG_OK;
llx = fllx;
lly = flly;
urx = furx;
ury = fury;
}
else status = IG_BADSIZE;
}
}
}
/* report error or calculate true size, depending on status */
switch( status )
{
case IG_NOFILE:
Error(12, 5, "%s deleted (cannot open file %s)", WARN, &fpos(x),
type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
string(full_name));
incgraphic_ok(x) = FALSE;
back(x, COLM) = fwd(x, COLM) = back(x, ROWM) = fwd(x, ROWM) = 0;
break;
case IG_LOOKING:
Error(12, 6, "%s given zero size (no BoundingBox line in file %s)",
WARN, &fpos(x),
type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
string(full_name));
back(y, COLM) = fwd(y, COLM) = back(y, ROWM) = fwd(y, ROWM) = 0;
back(x, COLM) = fwd(x, COLM) = back(x, ROWM) = fwd(x, ROWM) = 0;
incgraphic_ok(x) = TRUE;
fclose(fp);
if( cp ) StringRemove(AsciiToFull(LOUT_EPS));
break;
case IG_BADFILE:
Error(12, 7, "%s deleted (bad first line in file %s)", WARN,
&fpos(x), type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
string(full_name));
incgraphic_ok(x) = FALSE;
back(x, COLM) = fwd(x, COLM) = back(x, ROWM) = fwd(x, ROWM) = 0;
fclose(fp);
if( cp ) StringRemove(AsciiToFull(LOUT_EPS));
break;
case IG_BADSIZE:
Error(12, 8, "%s given zero size (bad BoundingBox line in file %s)",
WARN, &fpos(x),
type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
string(full_name));
back(y, COLM) = fwd(y, COLM) = back(y, ROWM) = fwd(y, ROWM) = 0;
back(x, COLM) = fwd(x, COLM) = back(x, ROWM) = fwd(x, ROWM) = 0;
incgraphic_ok(x) = TRUE;
fclose(fp);
if( cp ) StringRemove(AsciiToFull(LOUT_EPS));
break;
case IG_OK:
Child(y, Down(x));
back(y, COLM) = llx; fwd(y, COLM) = urx;
back(y, ROWM) = lly; fwd(y, ROWM) = ury;
b = (urx - llx) * PT;
b = find_min(MAX_FULL_LENGTH, find_max(0, b));
back(x, COLM) = fwd(x, COLM) = b / 2;
b = (ury - lly) * PT;
b = find_min(MAX_FULL_LENGTH, find_max(0, b));
back(x, ROWM) = fwd(x, ROWM) = b / 2;
incgraphic_ok(x) = TRUE;
fclose(fp);
if( cp ) StringRemove(AsciiToFull(LOUT_EPS));
break;
}
DisposeObject(full_name);
break;
default:
assert1(FALSE, "MinSize", Image(type(x)));
break;
} /* end switch */
debugcond6(DSF, D, dim == COLM && --debug_depth < debug_depth_max,
"%*s] MinSize(COLM, %s %d) = (%s, %s)", debug_depth*2, " ", Image(type(x)),
(int) x, EchoLength(back(x, dim)), EchoLength(fwd(x, dim)));
debug1(DSF, DD, "] MinSize returning, x = %s", EchoObject(x));
debug3(DSF, DD, " (%s size is %s, %s)", dimen(dim),
EchoLength(back(x, dim)), EchoLength(fwd(x, dim)) );
ifdebug(DSF, DDD, DebugObject(x));
assert(back(x, dim) >= 0, "MinSize: back(x, dim) < 0!");
assert(fwd(x, dim) >= 0, "MinSize: fwd(x, dim) < 0!");
return x;
} /* end MinSize */