| /*@z44.c:Vertical Hyphenation:VerticalHyphenate()@****************************/ |
| /* */ |
| /* 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: z44.c */ |
| /* MODULE: Vertical Hyphenation */ |
| /* EXTERNS: VerticalHyphenate(), ConvertGalleyList() */ |
| /* */ |
| /*****************************************************************************/ |
| #include "externs.h" |
| |
| |
| /*****************************************************************************/ |
| /* */ |
| /* FirstDefiniteCompressed(x, link, y) */ |
| /* NextDefiniteWithGapCompressed(x, link, y, g) */ |
| /* */ |
| /* Like FirstDefinite() and NextDefiniteWithGap(), except that these */ |
| /* versions assume that x is of type VCAT, and they compress any VCAT */ |
| /* objects found within x as they go. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| #define FirstDefiniteCompressed(x, link, y) \ |
| { BOOLEAN jn; \ |
| FirstDefinite(x, link, y, jn); \ |
| while( link != x && type(y) == VCAT ) \ |
| { TransferLinks(Down(y), y, link); \ |
| DisposeChild(link); \ |
| FirstDefinite(x, link, y, jn); \ |
| } \ |
| assert( link==x || is_definite(type(y)), "FirstDefiniteCompressed!"); \ |
| } |
| |
| #define NextDefiniteWithGapCompressed(x, link, y, g) \ |
| { OBJECT start_link = PrevDown(link), ylink, yg, z; \ |
| BOOLEAN jn; \ |
| NextDefiniteWithGap(x, link, y, g, jn); \ |
| while( link != x && type(y) == VCAT ) \ |
| { FirstDefinite(y, ylink, z, jn); \ |
| if( ylink != y && PrevDown(ylink) != y ) \ |
| { Child(yg, PrevDown(ylink)); \ |
| assert( type(yg)==GAP_OBJ && mode(gap(yg)) != NO_MODE, "NDWGC!"); \ |
| MoveLink(PrevDown(ylink), Up(g), PARENT); \ |
| MoveLink(Up(g), ylink, PARENT); \ |
| } \ |
| TransferLinks(Down(y), y, link); \ |
| DisposeChild(link); \ |
| link = NextDown(start_link); \ |
| NextDefiniteWithGap(x, link, y, g, jn); \ |
| } \ |
| assert( link==x || is_definite(type(y)), "FirstDefiniteCompressed!"); \ |
| assert( link==x || mode(gap(g)) != NO_MODE, \ |
| "FirstDefiniteWithGapCompressed: mode(gap(g))!" ); \ |
| } |
| |
| |
| /*@@**************************************************************************/ |
| /* */ |
| /* OBJECT FindTarget(index) */ |
| /* */ |
| /* Work out what the given index is pointing at, or nilobj if nothing. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| static OBJECT FindTarget(OBJECT index) |
| { OBJECT res; |
| debug1(DVH, DD, "FindTarget(%s)", Image(type(index))); |
| switch( type(index) ) |
| { |
| case DEAD: |
| |
| res = nilobj; |
| break; |
| |
| |
| case UNATTACHED: |
| case GALL_PREC: |
| case GALL_FOLL: |
| case GALL_FOLL_OR_PREC: |
| |
| res = pinpoint(index); |
| break; |
| |
| |
| case RECEPTIVE: |
| case RECEIVING: |
| case RECURSIVE: |
| case SCALE_IND: |
| case COVER_IND: |
| case EXPAND_IND: |
| |
| res = actual(index); |
| break; |
| |
| |
| case PRECEDES: |
| case FOLLOWS: |
| case CROSS_TARG: |
| case CROSS_PREC: |
| case CROSS_FOLL: |
| case CROSS_FOLL_OR_PREC: |
| case PAGE_LABEL_IND: |
| |
| res = nilobj; /* somewhat doubtful */ |
| break; |
| |
| |
| case GALL_TARG: |
| |
| res = nilobj; /* somewhat doubtful */ |
| break; |
| |
| |
| default: |
| |
| assert1(FALSE, "FindTarget: unknown index", Image(type(index))); |
| break; |
| } |
| debug1(DVH, DD, "FindTarget returning %s", EchoObject(res)); |
| return res; |
| } /* end FindTarget */ |
| |
| |
| /*@@**************************************************************************/ |
| /* */ |
| /* OBJECT WhichComponent(target) */ |
| /* */ |
| /* Return the component of the enclosing galley that contains target, */ |
| /* or nilobj if some problem. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| static OBJECT WhichComponent(OBJECT target) |
| { OBJECT prnt; |
| debug1(DVH, DD, "WhichComponent(%s)", EchoObject(target)); |
| while( Up(target) != target ) |
| { Parent(prnt, Up(target)); |
| if( type(prnt) == HEAD ) |
| { debug1(DVH, DD, "WhichComponent returning %s", EchoObject(target)); |
| return target; |
| } |
| target = prnt; |
| } |
| debug0(DVH, DD, "WhichComponent returning nilobj"); |
| return nilobj; |
| } /* end WhichComponent */ |
| |
| |
| /*****************************************************************************/ |
| /* */ |
| /* OBJECT EncloseInHcat(nxt, y, replace) */ |
| /* */ |
| /* Enclose object nxt in an HCAT, similar to HCAT y, at position replace. */ |
| /* The link to nxt will now link to the new HCAT. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| static OBJECT EncloseInHcat(OBJECT nxt, OBJECT y, OBJECT replace) |
| { OBJECT new_y, new_row_thread, s1, new_s1, s2, new_s2, link, sh, new_sh, tmp; |
| assert( Up(nxt) != nxt, "EncloseInHCat: Up(nxt) == nxt!" ); |
| New(new_y, HCAT); |
| adjust_cat(new_y) = FALSE; |
| MoveLink(Up(nxt), new_y, CHILD); |
| assert( Up(nxt) == nxt, "EncloseInHCat: Up(nxt) != nxt!" ); |
| FposCopy(fpos(new_y), fpos(y)); |
| back(new_y, COLM) = back(y, COLM); |
| fwd(new_y, COLM) = fwd(y, COLM); |
| back(new_y, ROWM) = back(nxt, ROWM); |
| fwd(new_y, ROWM) = fwd(nxt, ROWM); |
| New(new_row_thread, ROW_THR); |
| back(new_row_thread, ROWM) = back(new_y, ROWM); |
| fwd(new_row_thread, ROWM) = fwd(new_y, ROWM); |
| thr_state(new_row_thread) = SIZED; |
| for( link = Down(y); link != y; link = NextDown(link) ) |
| { Child(s1, link); |
| if( type(s1) == GAP_OBJ ) |
| { New(new_s1, GAP_OBJ); |
| FposCopy(fpos(new_s1), fpos(s1)); |
| GapCopy(gap(new_s1), gap(s1)); |
| hspace(new_s1) = hspace(s1); |
| vspace(new_s1) = vspace(s1); |
| Link(new_y, new_s1); |
| continue; |
| } |
| if( type(s1) == WIDE || type(s1) == ONE_COL ) |
| Child(s2, Down(s1)); |
| else s2 = s1; |
| assert( type(s2) == SPLIT, "EncloseInHcat: type(s2) != SPLIT!" ); |
| Child(sh, DownDim(s2, COLM)); |
| New(new_s2, SPLIT); |
| FposCopy(fpos(new_s2), fpos(s2)); |
| if( s2 != s1 ) |
| { New(new_s1, type(s1)); |
| back(new_s1, COLM) = back(s1, COLM); |
| fwd(new_s1, COLM) = fwd(s1, COLM); |
| back(new_s1, ROWM) = back(new_row_thread, COLM); |
| fwd(new_s1, ROWM) = fwd(new_row_thread, COLM); |
| Link(new_y, new_s1); |
| Link(new_s1, new_s2); |
| } |
| else Link(new_y, new_s2); |
| if( sh == replace ) |
| { |
| /* replace sh by nxt in the copy */ |
| new_sh = nxt; |
| back(new_sh, COLM) = back(s2, COLM); |
| fwd(new_sh, COLM) = fwd(s2, COLM); |
| } |
| else |
| { |
| /* replace sh by an empty object of the same width in the copy */ |
| New(new_sh, WIDE); |
| FposCopy(fpos(new_sh), fpos(sh)); |
| SetConstraint(constraint(new_sh), back(sh,COLM),size(sh,COLM),fwd(sh,COLM)); |
| back(new_sh, COLM) = back(sh, COLM); |
| fwd(new_sh, COLM) = fwd(sh, COLM); |
| back(new_sh, ROWM) = fwd(new_sh, ROWM) = 0; |
| tmp = MakeWord(WORD, STR_EMPTY, &fpos(sh)); |
| back(tmp, COLM) = fwd(tmp, COLM) = 0; |
| back(tmp, ROWM) = fwd(tmp, ROWM) = 0; |
| underline(tmp) = UNDER_OFF; |
| Link(new_sh, tmp); |
| } |
| Link(new_s2, new_sh); |
| back(new_s2, COLM) = back(new_sh, COLM); |
| fwd(new_s2, COLM) = fwd(new_sh, COLM); |
| Link(new_s2, new_row_thread); |
| back(new_s2, ROWM) = back(new_row_thread, ROWM); |
| fwd(new_s2, ROWM) = fwd(new_row_thread, ROWM); |
| Link(new_row_thread, new_sh); |
| } |
| return new_y; |
| } /* end EncloseInHcat */ |
| |
| |
| /*@::VerticalHyphenate()@*****************************************************/ |
| /* */ |
| /* BOOLEAN VerticalHyphenate(OBJECT y) */ |
| /* */ |
| /* Attempt to vertically hyphenate galley component y, of type HCAT. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| BOOLEAN VerticalHyphenate(OBJECT y) |
| { OBJECT large_comp, index, z, link, g; |
| OBJECT row_thread, s1, s2, sh, sv, shp, prev, nxt, large_comp_split; |
| FULL_LENGTH rump_fwd; |
| debug1(DVH, D, "[ VerticalHyphenate(y: %s), y =", EchoLength(size(y, ROWM))); |
| ifdebug(DVH, D, DebugObject(y)); |
| debug0(DVH, DD, "galley before vertical hyphenation:"); |
| ifdebug(DVH, DD, Parent(z, Up(y)); DebugGalley(z, y, 2)); |
| |
| /* find large_comp, the largest VCAT component, or else return FALSE */ |
| row_thread = large_comp = nilobj; |
| rump_fwd = 0; |
| assert( type(y) == HCAT, "VerticalHyphenate: type(y) != HCAT!" ); |
| for( link = Down(y); link != y; link = NextDown(link) ) |
| { Child(s1, link); |
| if( type(s1) == GAP_OBJ ) |
| { if( !join(gap(s1)) ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (not joined)"); |
| return FALSE; |
| } |
| continue; |
| } |
| |
| /* check that s2 is a SPLIT object whose children look right */ |
| if( type(s1) == WIDE || type(s1) == ONE_COL ) |
| Child(s2, Down(s1)); |
| else s2 = s1; |
| if( type(s2) != SPLIT ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (child not SPLIT)"); |
| return FALSE; |
| } |
| Child(sh, DownDim(s2, COLM)); |
| Child(sv, DownDim(s2, ROWM)); |
| if( type(sv) != ROW_THR ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (no ROW_THR)"); |
| return FALSE; |
| } |
| if( row_thread == nilobj ) row_thread = sv; |
| if( sv != row_thread ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (different ROW_THR)"); |
| return FALSE; |
| } |
| Parent(shp, UpDim(sh, ROWM)); |
| if( shp != row_thread ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (sh parent)"); |
| return FALSE; |
| } |
| |
| /* Now sh is one of the HCAT components */ |
| if( type(sh) != VCAT ) |
| { rump_fwd = find_max(rump_fwd, fwd(sh, ROWM)); |
| } |
| else if( large_comp != nilobj ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (two VCATs)"); |
| return FALSE; |
| } |
| else |
| { large_comp = sh; |
| large_comp_split = s2; |
| } |
| } |
| |
| /* if no large_comp, return */ |
| if( large_comp == nilobj ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (no VCAT)"); |
| return FALSE; |
| } |
| |
| /* check that large_comp has at least two components */ |
| FirstDefiniteCompressed(large_comp, link, prev); |
| if( link == large_comp ) |
| { debug0(DVH,D, "] VerticalHyphenate returning FALSE (VCAT: no components)"); |
| return FALSE; |
| } |
| NextDefiniteWithGapCompressed(large_comp, link, nxt, g); |
| if( link == large_comp ) |
| { debug0(DVH,D, "] VerticalHyphenate returning FALSE (VCAT: one component)"); |
| return FALSE; |
| } |
| |
| /* make sure that first gap does not change when rearranging */ |
| rump_fwd = find_max(rump_fwd, fwd(prev, ROWM)); |
| if( MinGap(rump_fwd, back(nxt, ROWM), fwd(nxt, ROWM), &gap(g)) != |
| MinGap(fwd(prev, ROWM), back(nxt, ROWM), fwd(nxt, ROWM), &gap(g)) ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (first gap changes)"); |
| return FALSE; |
| } |
| |
| /* check that large_comp has no joins */ |
| for( link = Down(large_comp); link != large_comp; link = NextDown(link) ) |
| { Child(z, link); |
| if( type(z) == GAP_OBJ && mode(gap(z)) != NO_MODE && join(gap(z)) ) |
| { debug0(DVH, D, "] VerticalHyphenate returning FALSE (VCAT: joined)"); |
| return FALSE; |
| } |
| } |
| |
| /* enclose all definite components after the first in HCATs */ |
| for( link = NextDown(Up(prev)); link != large_comp; link = NextDown(link) ) |
| { Child(nxt, link); |
| if( type(nxt) == GAP_OBJ ) continue; |
| if( is_definite(type(nxt)) ) |
| nxt = EncloseInHcat(nxt, y, large_comp); |
| } |
| |
| /* move all components after the first to the top level */ |
| TransferLinks(Up(g), large_comp, NextDown(Up(y))); |
| |
| /* change the size of y to its new, smaller value */ |
| fwd(y, ROWM) = fwd(row_thread, ROWM) = fwd(large_comp, ROWM) |
| = fwd(large_comp_split, ROWM) = fwd(prev, ROWM); |
| |
| /* set link to the link of the first thing before y which is not an index */ |
| for( link = PrevDown(Up(y)); type(link) == LINK; link = PrevDown(link) ) |
| { Child(index, link); |
| if( !is_index(type(index)) ) break; |
| } |
| |
| /* for each index, find where it's pointing and possibly move it */ |
| while( NextDown(link) != Up(y) ) |
| { Child(index, NextDown(link)); |
| assert( is_index(type(index)), "MoveIndexes: is_index!" ); |
| z = FindTarget(index); |
| if( z != nilobj ) |
| { z = WhichComponent(z); |
| if( z != nilobj && z != y ) |
| { MoveLink(NextDown(link), Up(z), PARENT); |
| } |
| else link = NextDown(link); |
| } |
| else link = NextDown(link); |
| } |
| |
| debug1(DVH, D, "] VerticalHyphenate returning TRUE (y: %s)", |
| EchoLength(size(y, ROWM))); |
| debug0(DVH, DD, "galley after vertical hyphenation:"); |
| ifdebug(DVH, DD, Parent(z, Up(y)); DebugGalley(z, y, 2)); |
| return TRUE; |
| } /* end VerticalHyphenate */ |
| |
| |
| /*****************************************************************************/ |
| /* */ |
| /* static OBJECT BuildMergeTree(int n, OBJECT x, OBJECT *lenv, *lact) */ |
| /* */ |
| /* Build a balanced tree of n-1 @Merge symbols, whose parameters are the */ |
| /* first n children of x. Return in lenv the environment of the root */ |
| /* @Merge symbol, and in *lact the symbol table entry for the parent of */ |
| /* this @Merge symbol. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| static OBJECT BuildMergeTree(int n, OBJECT x, OBJECT *lenv, OBJECT *lact) |
| { OBJECT res, merge, link, y, l, r, env, act, left_par, right_par; |
| debug2(DHY, DD, "BuildMergeTree(%d, %s, -. -)", n, EchoObject(x)); |
| |
| if( n == 1 ) |
| { New(res, ENV_OBJ); |
| Child(y, Down(x)); |
| MoveLink(Down(x), res, PARENT); |
| assert(type(y)==CLOSURE && has_merge(actual(y)), "BuildMergeTree: has_m!"); |
| *lact = actual(y); |
| *lenv = DetachEnv(y); |
| AttachEnv(*lenv, res); |
| } |
| else |
| { |
| /* build the two subtrees */ |
| l = BuildMergeTree(n/2, x, lenv, lact); |
| r = BuildMergeTree( n - n/2, x, &env, &act); |
| |
| /* set merge to new @Merge closure */ |
| for( link = Down(act); link != act; link = NextDown(link) ) |
| { Child(y, link); |
| if( is_merge(y) ) break; |
| } |
| assert( y != act, "BuildMergeTree: y!" ); |
| New(merge, CLOSURE); |
| actual(merge) = y; |
| |
| /* build left parameter of the new @Merge */ |
| New(left_par, PAR); |
| actual(left_par) = ChildSym(y, LPAR); |
| Link(merge, left_par); |
| Link(left_par, l); |
| |
| /* build right parameter of the new @Merge */ |
| New(right_par, PAR); |
| actual(right_par) = ChildSym(y, RPAR); |
| Link(merge, right_par); |
| Link(right_par, r); |
| |
| New(res, ENV_OBJ); |
| Link(res, merge); |
| Link(res, env); |
| } |
| |
| debug2(DHY, DD, "BuildMergeTree returning %s (*lact = %s)", |
| EchoObject(res), SymName(*lact)); |
| return res; |
| } /* end BuildMergeTree */ |
| |
| |
| /*****************************************************************************/ |
| /* */ |
| /* OBJECT ConvertGalleyList(x) */ |
| /* */ |
| /* Convert a set of galleys x into a single galley containing a balanced */ |
| /* tree of @Merge symbols. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| OBJECT ConvertGalleyList(OBJECT x) |
| { OBJECT res, y, link, junk1, junk2, obj; int n; |
| debug1(DHY, DD, "ConvertGalleyList(%s)", EchoObject(x)); |
| Child(res, Down(x)); |
| Child(y, Down(res)); |
| MoveLink(Down(x), y, CHILD); |
| DeleteLink(Down(res)); |
| MoveLink(Up(x), res, CHILD); |
| for( link = Down(x), n = 0; link != x; link = NextDown(link), n++ ); |
| y = BuildMergeTree(n, x, &junk1, &junk2); |
| assert( Down(x) == x && Up(x) == x, "ConvertGalleyList: x!" ); |
| Dispose(x); |
| Child(obj, Down(y)); |
| MoveLink(Down(y), res, PARENT); |
| MoveLink(LastDown(y), obj, PARENT); |
| assert( Down(y) == y && Up(y) == y, "ConvertGalleyList: y!" ); |
| Dispose(y); |
| debug0(DHY, DD, "ConvertGalleyList returning, res ="); |
| ifdebug(DHY, DD, DebugObject(res)); |
| return res; |
| } /* end ConvertGalleyList */ |
| |
| |
| /*****************************************************************************/ |
| /* */ |
| /* OBJECT BuildEnclose(hd) */ |
| /* */ |
| /* Build the @Enclose object for galley hd. */ |
| /* */ |
| /*****************************************************************************/ |
| |
| OBJECT BuildEnclose(OBJECT hd) |
| { OBJECT sym, parsym, x, y, link, par, val, env, res; |
| debug1(DOM, D, "BuildEnclose(%s)", SymName(actual(hd))); |
| |
| /* find @Enclose symbol and check that it has just one parameter */ |
| for( link = Down(actual(hd)); link != actual(hd); link = NextDown(link) ) |
| { Child(sym, link); |
| if( is_enclose(sym) ) break; |
| } |
| assert( link != actual(hd), "BuildEnclose: no enclose!" ); |
| parsym = nilobj; |
| for( link = Down(sym); link != sym; link = NextDown(link) ) |
| { Child(y, link); |
| switch( type(y) ) |
| { |
| case LPAR: |
| case NPAR: |
| |
| Error(44, 1, "%s may not have a left or named parameter", FATAL, |
| &fpos(y), KW_ENCLOSE); |
| break; |
| |
| |
| case RPAR: |
| |
| if( has_body(sym) ) |
| Error(44, 2, "%s may not have a body parameter", FATAL, |
| &fpos(y), KW_ENCLOSE); |
| parsym = y; |
| break; |
| |
| |
| default: |
| |
| break; |
| } |
| } |
| if( parsym == nilobj ) |
| Error(44, 3, "%s must have a right parameter", FATAL, &fpos(sym),KW_ENCLOSE); |
| |
| /* set x to new @Enclose closure with dummy actual right parameter */ |
| New(x, CLOSURE); |
| FposCopy(fpos(x), fpos(hd)); |
| actual(x) = sym; |
| New(par, PAR); |
| FposCopy(fpos(par), fpos(hd)); |
| actual(par) = parsym; |
| Link(x, par); |
| val = MakeWord(WORD, AsciiToFull("??"), &fpos(hd)); |
| Link(par, val); |
| |
| /* set env to the appropriate environment for this symbol */ |
| /* strictly speaking y should not be included if sym is a parameter */ |
| Child(y, Down(hd)); |
| assert(type(y) == CLOSURE, "BuildEnclose: hd child!"); |
| y = CopyObject(y, &fpos(hd)); |
| env = SetEnv(y, nilobj); |
| |
| /* build res, an ENV_OBJ with x at left and env at right */ |
| New(res, ENV_OBJ); |
| Link(res, x); |
| Link(res, env); |
| |
| debug1(DOM, D, "BuildEnclose returning %s", EchoObject(res)); |
| return res; |
| } /* end BuildEnclose */ |