/*@z07.c:Object Service:SplitIsDefinite(), DisposeObject()@*******************/ /* */ /* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.41) */ /* COPYRIGHT (C) 1991, 2023 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: z07.c */ /* MODULE: Object Service */ /* EXTERNS: MakeWord(), MakeWordTwo(), MakeWordThree(), */ /* DisposeObject(), CopyObject(), */ /* SplitIsDefinite(), InsertObject() */ /* */ /*****************************************************************************/ #include "externs.h" #include "child.h" #include "count_child.h" /*****************************************************************************/ /* */ /* BOOLEAN SplitIsDefinite(x) */ /* */ /* Return TRUE if x is a definite SPLIT object (both children definite) */ /* */ /*****************************************************************************/ BOOLEAN SplitIsDefinite(OBJECT x) { OBJECT y1, y2; assert( type(x) == SPLIT, "SplitIsDefinite: x not a SPLIT!" ); Child(y1, DownDim(x, COLM)); Child(y2, DownDim(x, ROWM)); return is_definite(type(y1)) && is_definite(type(y2)); } /* end SplitIsDefinite */ /*****************************************************************************/ /* */ /* DisposeSplitObject(x) */ /* */ /* Dispose SPLIT object x, taking care to handle COL_THR and ROW_THR */ /* children properly. */ /* */ /*****************************************************************************/ static void DisposeSplitObject(OBJECT x) { int i, count; OBJECT y, link, uplink; debug1(DOS, DDD, "[ DisposeSplitObject( %ld )", (long) x); assert(type(x) == SPLIT, "DisposeSplitObject: type(x) != SPLIT!"); assert(Down(x) != x, "DisposeSplitObject: x has no children!") assert(LastDown(x) != Down(x), "DisposeSplitObject: x has one child!") assert(LastDown(x) == NextDown(Down(x)), "DisposeSplitObject: children!") /* handle first child */ CountChild(y, Down(x), count); if( type(y) == COL_THR ) { /* find corresponding child link out of y and delete that link */ for( link = Down(y), uplink = Up(y), i = 1; link != y && uplink != y && i < count; link = NextDown(link), uplink = NextUp(uplink), i++ ); assert( link != y && uplink != y, "DisposeSplitObject: link (a)!" ); DisposeChild(link); } DisposeChild(Down(x)); /* handle second child */ CountChild(y, LastDown(x), count); if( type(y) == ROW_THR ) { /* find corresponding child link out of y and delete that link */ for( link = Down(y), uplink = Up(y), i = 1; link != y && uplink != y && i < count; link = NextDown(link), uplink = NextUp(uplink), i++ ); assert( link != y && uplink != y, "DisposeSplitObject: link (b)!" ); DisposeChild(link); } DisposeChild(LastDown(x)); debug0(DOS, DDD, "] DisposeSplitObject returning"); } /* end DisposeSplitObject */ /*****************************************************************************/ /* */ /* DisposeObject(x) */ /* */ /* Dispose object x recursively, leaving intact any shared descendants. */ /* We return a useless integer so that we can use this in expresssions. */ /* */ /* If x is a SPLIT object then one or both of its children could be */ /* COL_THR or ROW_THR objects. If such thread object is has this SPLIT */ /* as its ith parent, then we need to dispose its ith child. */ /* */ /*****************************************************************************/ int DisposeObject(OBJECT x) { debug2(DOS,DDD,"[DisposeObject( %ld ), type = %s, x =", (long) x, Image(type(x))); ifdebug(DOS, DDD, DebugObject(x)); assert( Up(x) == x, "DisposeObject: x has a parent!" ); if( type(x) == SPLIT ) DisposeSplitObject(x); else { while( Down(x) != x ) DisposeChild(Down(x)); Dispose(x); } debug0(DOS, DDD, "]DisposeObject returning."); return 0; } /* end DisposeObject */ /*@::MakeWord(), MakeWordTwo()@***********************************************/ /* */ /* OBJECT MakeWord(typ, str, pos) */ /* */ /* Return an unsized WORD or QWORD made from the given string and fpos. */ /* */ /*****************************************************************************/ OBJECT MakeWord(unsigned typ, FULL_CHAR *str, FILE_POS *pos) { OBJECT res; NewWord(res, typ, StringLength(str), pos); StringCopy(string(res), str); FposCopy(fpos(res), *pos); debug4(DOS, DDD, "MakeWord(%s, %s, %s) returning %s", Image(typ), str, EchoFilePos(pos), EchoObject(res)); return res; } /* end MakeWord */ /*****************************************************************************/ /* */ /* OBJECT MakeWordTwo(typ, str1, str2, pos) */ /* */ /* Return an unsized WORD or QWORD made from the two strings and fpos. */ /* */ /*****************************************************************************/ OBJECT MakeWordTwo(unsigned typ, FULL_CHAR *str1, FULL_CHAR *str2, FILE_POS *pos) { int len1 = StringLength(str1); int len2 = StringLength(str2); OBJECT res; debug4(DOS, DDD, "MakeWordTwo(%s, %s, %s, %s)", Image(typ), str1, str2, EchoFilePos(pos)); NewWord(res, typ, len1 + len2, pos); StringCopy(string(res), str1); StringCopy(&string(res)[len1], str2); FposCopy(fpos(res), *pos); debug5(DOS, DDD, "MakeWordTwo(%s, %s, %s, %s) returning %s", Image(typ), str1, str2, EchoFilePos(pos), EchoObject(res)); return res; } /* end MakeWordTwo */ /*****************************************************************************/ /* */ /* OBJECT MakeWordThree(s1, s2, s3) */ /* */ /* Return an unsized WORD containing these three strings. */ /* */ /*****************************************************************************/ OBJECT MakeWordThree(FULL_CHAR *s1, FULL_CHAR *s2, FULL_CHAR *s3) { int len1 = StringLength(s1); int len2 = StringLength(s2); int len3 = StringLength(s3); OBJECT res; debug3(DOS, DDD, "MakeWordThree(%s, %s, %s)", s1, s2, s3); NewWord(res, WORD, len1 + len2 + len3, no_fpos); StringCopy(string(res), s1); StringCopy(&string(res)[len1], s2); StringCopy(&string(res)[len1 + len2], s3); debug4(DOS, DDD, "MakeWordThree(%s, %s, %s) returning %s", s1, s2, s3, EchoObject(res)); return res; } /* end MakeWordThree */ /*@::CopyObject()@************************************************************/ /* */ /* OBJECT CopyObject(x, pos) */ /* */ /* Make a copy of unsized object x, setting all file positions to *pos, */ /* unless *pos is no_fpos, in which case set all file positions to what */ /* they are in the object being copied. */ /* */ /*****************************************************************************/ OBJECT CopyObject(OBJECT x, FILE_POS *pos) { OBJECT y, link, res, tmp; debug2(DOS, DDD, "[ CopyObject(%s, %s)", EchoObject(x), EchoFilePos(pos)); switch( type(x) ) { case WORD: case QWORD: NewWord(res, type(x), StringLength(string(x)), pos); StringCopy(string(res), string(x)); break; case GAP_OBJ: New(res, GAP_OBJ); GapCopy(gap(res), gap(x)); hspace(res) = hspace(x); vspace(res) = vspace(x); if( Down(x) != x ) { Child(y, Down(x)); tmp = CopyObject(y, pos); Link(res, tmp); } break; /* case HEAD: */ case NULL_CLOS: case PAGE_LABEL: case CROSS: case FORCE_CROSS: case BEGIN_HEADER: case END_HEADER: case SET_HEADER: case CLEAR_HEADER: case ONE_COL: case ONE_ROW: case WIDE: case HIGH: case HSHIFT: case VSHIFT: case HMIRROR: case VMIRROR: case HSCALE: case VSCALE: case HCOVER: case VCOVER: case SCALE: case KERN_SHRINK: case HCONTRACT: case VCONTRACT: case HLIMITED: case VLIMITED: case HEXPAND: case VEXPAND: case START_HVSPAN: case START_HSPAN: case START_VSPAN: case HSPAN: case VSPAN: case PADJUST: case HADJUST: case VADJUST: case ROTATE: case BACKGROUND: case RAW_VERBATIM: case VERBATIM: case CASE: case YIELD: case BACKEND: case XCHAR: case FONT: case SPACE: case YUNIT: case ZUNIT: case SET_CONTEXT: case GET_CONTEXT: case BREAK: case UNDERLINE: case UNDERLINE_COLOUR: case COLOUR: case TEXTURE: case OUTLINE: case LANGUAGE: case CURR_LANG: case CURR_FAMILY: case CURR_FACE: case CURR_YUNIT: case CURR_ZUNIT: case COMMON: case RUMP: case MELD: case INSERT: case ONE_OF: case NEXT: case PLUS: case MINUS: case OPEN: case TAGGED: case INCGRAPHIC: case SINCGRAPHIC: case PLAIN_GRAPHIC: case GRAPHIC: case LINK_SOURCE: case LINK_DEST: case LINK_DEST_NULL: case LINK_URL: case VCAT: case HCAT: case ACAT: case ENV_OBJ: New(res, type(x)); for( link = Down(x); link != x; link = NextDown(link) ) { Child(y, link); tmp = CopyObject(y, pos); Link(res, tmp); } break; case FILTERED: New(res, type(x)); for( link = Down(x); link != x; link = NextDown(link) ) { Child(y, link); Link(res, y); /* do not copy children of FILTERED */ } debug3(DFH, D, "copying FILTERED %d into %d %s", (int) x, (int) res, EchoObject(res)); break; case ENV: res = x; /* do not copy environments */ break; case PAR: New(res, PAR); actual(res) = actual(x); assert( Down(x) != x, "CopyObject: PAR child!" ); Child(y, Down(x)); tmp = CopyObject(y, pos); Link(res, tmp); break; case CLOSURE: New(res, CLOSURE); for( link = Down(x); link != x; link = NextDown(link) ) { Child(y, link); assert( type(y) != CLOSURE, "CopyObject: CLOSURE!" ); tmp = CopyObject(y, pos); Link(res, tmp); } actual(res) = actual(x); StyleCopy(save_style(res), save_style(x)); break; default: assert1(FALSE, "CopyObject:", Image(type(x))); res = nilobj; break; } /* end switch */ if( pos == no_fpos ) FposCopy(fpos(res), fpos(x)); else FposCopy(fpos(res), *pos); debug1(DOS, DDD, "] CopyObject returning %s", EchoObject(res)); return res; } /* end CopyObject */ /*****************************************************************************/ /* */ /* OBJECT InsertObject(OBJECT x, OBJECT *ins, STYLE *style) */ /* */ /* Search through manifested object x for an ACAT where ins may be */ /* attached. If successful, set *ins to nilobj after the attachment. */ /* */ /*****************************************************************************/ OBJECT InsertObject(OBJECT x, OBJECT *ins, STYLE *style) { OBJECT link, y, g, res; debug2(DOS, D, "InsertObject(%s, %s)", EchoObject(x), EchoObject(*ins)); switch( type(x) ) { case WORD: case QWORD: New(res, ACAT); FposCopy(fpos(res), fpos(x)); ReplaceNode(res, x); Link(res, x); StyleCopy(save_style(res), *style); adjust_cat(res) = padjust(*style); res = InsertObject(res, ins, style); break; case NULL_CLOS: case BEGIN_HEADER: case END_HEADER: case SET_HEADER: case CLEAR_HEADER: case HEAD: case CROSS: case FORCE_CROSS: case PAGE_LABEL: case CLOSURE: case INCGRAPHIC: case SINCGRAPHIC: case HSPAN: case VSPAN: res = x; break; case HCAT: case VCAT: case COL_THR: case ROW_THR: case SPLIT: for( link = Down(x); link != x && *ins != nilobj; link = NextDown(link) ) { Child(y, link); y = InsertObject(y, ins, style); } res = x; break; case ONE_COL: case ONE_ROW: case PADJUST: case HADJUST: case VADJUST: case HCONTRACT: case VCONTRACT: case HLIMITED: case VLIMITED: case HEXPAND: case VEXPAND: case HMIRROR: case VMIRROR: case HSCALE: case VSCALE: case HCOVER: case VCOVER: case PLAIN_GRAPHIC: case GRAPHIC: case LINK_SOURCE: case LINK_DEST: case LINK_DEST_NULL: case LINK_URL: case ROTATE: case BACKGROUND: case SCALE: case KERN_SHRINK: case WIDE: case HIGH: case HSHIFT: case VSHIFT: case START_HVSPAN: case START_HSPAN: case START_VSPAN: Child(y, LastDown(x)); y = InsertObject(y, ins, style); res = x; break; case ACAT: New(g, GAP_OBJ); SetGap(gap(g), FALSE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0); hspace(g) = vspace(g) = 0; underline(g) = UNDER_OFF; Link(Down(x), g); Link(Down(x), *ins); underline(*ins) = UNDER_OFF; *ins = nilobj; res = x; break; default: assert1(FALSE, "InsertObject:", Image(type(x))); res = x; break; } debug2(DOS, D, "InsertObject returning (%s) %s", *ins == nilobj ? "success" : "failure", EchoObject(res)); return res; } /* end InsertObject */ /*****************************************************************************/ /* */ /* Meld(x, y) */ /* */ /* Return the meld of x with y. */ /* */ /*****************************************************************************/ #define NO_DIR 0 #define X_DIR 1 #define Y_DIR 2 #define XY_DIR 3 #define MAX_MELD 32 OBJECT Meld(OBJECT x, OBJECT y) { OBJECT res; char table[MAX_MELD][MAX_MELD], dir[MAX_MELD][MAX_MELD]; OBJECT xcomp[MAX_MELD], ycomp[MAX_MELD]; OBJECT xgaps[MAX_MELD], ygaps[MAX_MELD]; BOOLEAN is_equal; OBJECT link, z = nilobj, g; BOOLEAN jn; int xlen, ylen, xi, yi; debug2(DOS, D, "Meld(%s, %s)", EchoObject(x), EchoObject(y)); assert(type(x) == ACAT, "Meld: type(x) != ACAT"); assert(type(y) == ACAT, "Meld: type(y) != ACAT"); /* initialize xcomp, xgaps, xlen */ debug0(DOS, DD, " initializing xcomp[]"); xlen = 0; xcomp[xlen] = nilobj; xlen++; g = nilobj; FirstDefinite(x, link, z, jn); while( link != x ) { if( xlen >= MAX_MELD ) Error(7, 1, "%s: maximum paragraph length (%d) exceeded", FATAL, &fpos(x), KW_MELD, MAX_MELD-1); assert( type(z) != ACAT, "Meld: xcomp is ACAT!"); if( g == nilobj || width(gap(g)) != 0 ) { debug3(DOS, DD, " initializing xcomp[%d] to %s %s", xlen, Image(type(z)), EchoObject(z)); xcomp[xlen] = z; xgaps[xlen] = g; xlen++; } else { debug3(DOS, DD, " extending xcomp[%d] with %s %s", xlen-1, Image(type(z)), EchoObject(z)); if( type(xcomp[xlen-1]) != ACAT ) { New(res, ACAT); StyleCopy(save_style(res), save_style(x)); Link(res, xcomp[xlen-1]); xcomp[xlen-1] = res; } Link(xcomp[xlen-1], g); Link(xcomp[xlen-1], z); } NextDefiniteWithGap(x, link, z, g, jn) } /* initialize ycomp, ygaps, ylen */ debug0(DOS, DD, " initializing ycomp[]"); ylen = 0; ycomp[ylen] = nilobj; ylen++; g = nilobj; FirstDefinite(y, link, z, jn); while( link != y ) { if( ylen >= MAX_MELD ) Error(7, 1, "%s: maximum paragraph length (%d) exceeded", FATAL, &fpos(y), KW_MELD, MAX_MELD-1); assert( type(z) != ACAT, "Meld: ycomp is ACAT!"); if( g == nilobj || width(gap(g)) != 0 ) { debug3(DOS, DD, " initializing ycomp[%d] to %s %s", ylen, Image(type(z)), EchoObject(z)); ycomp[ylen] = z; ygaps[ylen] = g; ylen++; } else { debug3(DOS, DD, " extending ycomp[%d] with %s %s", ylen-1, Image(type(z)), EchoObject(z)); if( type(ycomp[ylen-1]) != ACAT ) { New(res, ACAT); StyleCopy(save_style(res), save_style(x)); Link(res, ycomp[ylen-1]); ycomp[ylen-1] = res; } Link(ycomp[ylen-1], g); Link(ycomp[ylen-1], z); } NextDefiniteWithGap(y, link, z, g, jn) } /* initialize table and dir */ debug0(DOS, DD, " initializing table[]"); table[0][0] = 0; dir[0][0] = NO_DIR; for( xi = 1; xi < xlen; xi++ ) { table[xi][0] = 0; dir[xi][0] = X_DIR; } for( yi = 1; yi < ylen; yi++ ) { table[0][yi] = 0; dir[0][yi] = Y_DIR; } for( xi = 1; xi < xlen; xi++ ) { for( yi = 1; yi < ylen; yi++ ) { is_equal = EqualManifested(xcomp[xi], ycomp[yi]); if( is_equal ) { table[xi][yi] = 1 + table[xi - 1][yi - 1]; dir[xi][yi] = XY_DIR; debug3(DOS, DD, " assigning (XY) table[%d][%d] = %d", xi, yi, table[xi][yi]); } else if( table[xi - 1][yi] > table[xi][yi - 1] ) { table[xi][yi] = table[xi - 1][yi]; dir[xi][yi] = X_DIR; debug3(DOS, DD, " assigning (X) table[%d][%d] = %d", xi, yi, table[xi][yi]); } else { table[xi][yi] = table[xi][yi - 1]; dir[xi][yi] = Y_DIR; debug3(DOS, DD, " assigning (Y) table[%d][%d] = %d", xi, yi, table[xi][yi]); } } } /* traverse table from [xlen-l][ylen-1] back to [0][0], finding who's in */ debug0(DOS, DD, " traversing table[]"); New(res, ACAT); StyleCopy(save_style(res), save_style(x)); for( xi = xlen - 1, yi = ylen - 1; dir[xi][yi] != NO_DIR; ) { switch( dir[xi][yi] ) { case XY_DIR: debug3(DOS, DD, " at table[%d][%d] (XY) linking %s", xi, yi, EchoObject(xcomp[xi])); if( type(xcomp[xi]) != ACAT ) { Link(Down(res), xcomp[xi]); } else TransferLinks(Down(xcomp[xi]), xcomp[xi], Down(res)); g = xgaps[xi]; xi--; yi--; break; case Y_DIR: debug3(DOS, DD, " at table[%d][%d] (ydec) linking %s", xi, yi, EchoObject(ycomp[yi])); if( type(ycomp[yi]) != ACAT ) { Link(Down(res), ycomp[yi]); } else TransferLinks(Down(ycomp[yi]), ycomp[yi], Down(res)); g = ygaps[yi]; yi--; break; case X_DIR: debug3(DOS, DD, " at table[%d][%d] (xdec) linking %s", xi, yi, EchoObject(xcomp[xi])); if( type(xcomp[xi]) != ACAT ) { Link(Down(res), xcomp[xi]); } else TransferLinks(Down(xcomp[xi]), xcomp[xi], Down(res)); g = xgaps[xi]; xi--; } /* add gap if not last time; either g or one we make up */ if( dir[xi][yi] != NO_DIR ) { if( g == nilobj ) { OBJECT tmp; New(g, GAP_OBJ); hspace(g) = 1; vspace(g) = 0; FposCopy(fpos(g), *no_fpos); SetGap(gap(g), FALSE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, width(space_gap(save_style(res)))); tmp = MakeWord(WORD, AsciiToFull("1s"), &fpos(g)); Link(g, tmp); Link(Down(res), g); } else { assert(Up(g) == LastUp(g), "Meld: g!" ); Link(Down(res), g); } } } debug1(DOS, D, "Meld returning %s", EchoObject(res)); return res; } /*****************************************************************************/ /* */ /* static BOOLEAN EqualChildren(x, y) */ /* */ /* Return TRUE if manifested objects x and y have equal children. */ /* */ /*****************************************************************************/ static BOOLEAN EqualChildren(OBJECT x, OBJECT y) { OBJECT xl, yl, xc, yc; xl = Down(x), yl = Down(y); for( ; xl != x && yl != y; xl = NextDown(xl), yl = NextDown(yl) ) { Child(xc, xl); Child(yc, yl); if( !EqualManifested(xc, yc) ) return FALSE; } return xl == x && yl == y; } /*****************************************************************************/ /* */ /* BOOLEAN EqualManifested(x, y) */ /* */ /* Return TRUE if manifested objects x and y are equal. */ /* */ /*****************************************************************************/ BOOLEAN EqualManifested(OBJECT x, OBJECT y) { OBJECT xc, yc; if( is_word(type(x)) && is_word(type(y)) ) { return StringEqual(string(x), string(y)); } else if( type(x) != type(y) ) { return FALSE; } else switch( type(x) ) { case GAP_OBJ: /* objects are equal if the two gaps are equal */ return GapEqual(gap(x), gap(y)); break; case CLOSURE: /* objects are equal if it's the same symbol and same parameters */ if( actual(x) != actual(y) ) return FALSE; return EqualChildren(x, y); break; case PAGE_LABEL: case NULL_CLOS: case CROSS: case FORCE_CROSS: case HEAD: case SPLIT: case HSPANNER: case VSPANNER: case COL_THR: case ROW_THR: case ACAT: case HCAT: case VCAT: case HMIRROR: case VMIRROR: case HSCALE: case VSCALE: case BEGIN_HEADER: case SET_HEADER: case END_HEADER: case CLEAR_HEADER: case ONE_COL: case ONE_ROW: case HCOVER: case VCOVER: case HCONTRACT: case VCONTRACT: case HEXPAND: case VEXPAND: case START_HSPAN: case START_VSPAN: case START_HVSPAN: case HSPAN: case VSPAN: case KERN_SHRINK: case BACKGROUND: case GRAPHIC: case PLAIN_GRAPHIC: case LINK_DEST: case LINK_DEST_NULL: case LINK_URL: case INCGRAPHIC: case SINCGRAPHIC: case PAR: /* objects are equal if the children are equal */ return EqualChildren(x, y); break; case LINK_SOURCE: /* objects are equal if right children are equal */ Child(xc, LastDown(x)); Child(yc, LastDown(y)); return EqualManifested(xc, yc); break; case WIDE: case HIGH: /* objects are equal if constraints and children are equal */ return EqualConstraint(constraint(x), constraint(y)) && EqualChildren(x, y); break; case HSHIFT: case VSHIFT: /* objects are equal if constraints and children are equal */ return shift_type(x) == shift_type(y) && GapEqual(shift_gap(x), shift_gap(y)) && EqualChildren(x, y); break; case SCALE: /* objects are equal if constraints and children are equal */ return bc(constraint(x)) == bc(constraint(y)) && fc(constraint(x)) == fc(constraint(y)) && EqualChildren(x, y); break; case ROTATE: /* objects are equal if angle is equal and children are equal */ return sparec(constraint(x)) == sparec(constraint(y)) && EqualChildren(x, y); break; default: Error(7, 2, "EqualUnsized: type == %s", FATAL, &fpos(x), Image(type(x))); return FALSE; break; } }