/*@z14.c:Fill Service:Declarations@*******************************************/ /* */ /* 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: z14.c */ /* MODULE: Fill Service */ /* EXTERNS: FillObject() */ /* */ /*****************************************************************************/ #include "externs.h" #include "child.h" #define TOO_TIGHT_BAD 1048576 /* 2^21; badness of a too tight line */ #define TOO_LOOSE_BAD 65536 /* 2^16; the max badness of a too loose line */ #define TIGHT_BAD 4096 /* 2^12; the max badness of a tight line */ #define LOOSE_BAD 4096 /* 2^12; the max badness of a loose line */ #define HYPH_BAD 128 /* 2^ 7; threshold for calling hyphenation */ #define HYPH_BAD_INCR 16 /* 2 ^4: the badness of one hyphen */ #define WIDOW_BAD_INCR 128 /* 2 ^7: the badness of one widow word */ #define SQRT_TOO_LOOSE 512 /* 2^ 9; sqrt(TOO_LOOSE_BAD) (used to be) */ #define SQRT_TIGHT_BAD 128 /* 2^ 7; sqrt(TIGHT_BAD) (used to be) */ #define SQRT_LOOSE_BAD 128 /* 2^ 7; sqrt(LOOSE_BAD) (used to be) */ #define SQRT_TOO_TIGHT 8192 /* 2^13; sqrt(TOO_TIGHT_BAD) (used to be) */ #define MAX_EXPAND 1 #define MAX_SHRINK 4 typedef struct { OBJECT llink; /* link to gap before left end of interval */ OBJECT rlink; /* link to gap after right end of interval */ OBJECT cwid; /* link to current line width in multi case */ int nat_width; /* natural width of interval */ int space_width; /* natural width of spaces in the interval */ int badness; /* badness of this interval */ unsigned char class; /* badness class of this interval */ unsigned char tab_count; /* number of gaps with tab mode in interval */ int tab_pos; /* if tab_count > 0, this holds the position */ /* of the left edge of the object following */ /* the rightmost tab gap in the interval */ int width_to_tab; /* if tab_count > 0, the interval width up */ /* to but not including the rightmost tab */ } INTERVAL; /*****************************************************************************/ /* */ /* Badness classes */ /* */ /*****************************************************************************/ #define TOO_LOOSE 0 /* interval is too loose */ #define LOOSE 1 /* interval is loose but not too loose */ #define TIGHT 2 /* interval is tight but not too tight */ #define TOO_TIGHT 3 /* interval is too tight */ #define TAB_OVERLAP 4 /* interval has a tab and left part overlaps */ #define AT_END 5 /* interval ends at right end of paragraph */ #define UNBREAKABLE_LEFT 6 /* interval has an unbreakable gap at left */ #define UNBREAKABLE_RIGHT 7 /* interval has an unbreakable gap at right */ #define EMPTY_INTERVAL 8 /* interval is empty */ /*@::SetIntervalBadness()@****************************************************/ /* */ /* SetIntervalBadness(I) */ /* */ /* Private, calculates the badness and badness class of a non-empty */ /* interval. Does not take into account any unbreakable gap at either end. */ /* */ /*****************************************************************************/ #define SetIntervalBadness(I, max_width, etc_width) \ { OBJECT g; int badness; \ int col_width; \ \ /* initialize to saved badness of left-adjoining interval, if any */ \ /* and set width of column */ \ if( I.llink == x ) \ { col_width = (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : max_width; \ I.badness = 0; \ } \ else \ { col_width = (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : etc_width; \ Child(g, I.llink); \ I.badness = save_badness(g); \ } \ \ /* penalize widow lines, of the form [ &1rt ... ] */ \ if( I.tab_count > 0 ) \ { OBJECT glink = NextDown(NextDown(I.llink)); \ assert( type(glink) == LINK, "SIB: glink!"); \ Child(g, glink); \ if( type(g) == GAP_OBJ && mode(gap(g)) == TAB_MODE && \ units(gap(g)) == AVAIL_UNIT && width(gap(g)) == 1*FR ) \ I.badness += WIDOW_BAD_INCR; \ } \ \ if( col_width <= 0 ) \ { if( I.nat_width == 0 ) \ { I.class = TOO_LOOSE; \ I.badness += 0; \ } \ else \ { I.class = TIGHT; \ I.badness += TOO_TIGHT_BAD; \ } \ } \ else if( I.tab_count > 0 && I.width_to_tab > I.tab_pos ) \ { I.class = TAB_OVERLAP; \ I.badness += TOO_TIGHT_BAD; \ } \ else if( MAX_EXPAND*(col_width-I.nat_width) > 2*I.space_width ) \ { I.class = I.tab_count > 0 ? LOOSE : TOO_LOOSE; \ badness = (SQRT_TOO_LOOSE*(col_width - I.nat_width)) / col_width; \ I.badness += badness * badness; \ } \ else if( I.nat_width <= col_width ) \ { I.class = LOOSE; \ badness = (SQRT_LOOSE_BAD*(col_width - I.nat_width)) / col_width; \ I.badness += badness * badness; \ } \ else if( BackEnd->fractional_spacing_avail && allow_shrink && \ MAX_SHRINK*(I.nat_width-col_width) <= I.space_width ) \ { I.class = TIGHT; \ badness = (SQRT_TIGHT_BAD*(col_width - I.nat_width)) / col_width; \ I.badness += badness * badness; \ } \ else \ { I.class = TOO_TIGHT; \ /*** \ badness = (SQRT_TOO_TIGHT*(col_width-I.nat_width)) / col_width; \ I.badness += badness * badness; \ ***/ \ I.badness += TOO_TIGHT_BAD; \ } \ assert( I.badness >= 0, "SetIntervalBadness: badness < 0!" ); \ } /* end macro SetIntervalBadness */ /*@::MoveRightToGap()@********************************************************/ /* */ /* MoveRightToGap(I, x, rlink, right, max_width, etc_width, hyph_word) */ /* */ /* Private. Shared by IntervalInit and IntervalShiftRightEnd, for moving */ /* to the next gap to the right, setting save_space(newg), checking for */ /* hyphenation case, and setting the interval badness. */ /* */ /*****************************************************************************/ #define MoveRightToGap(I,x,rlink,right,max_width,etc_width,hyph_word) \ { OBJECT newg, foll = nilobj /* , tmp */; int ch; \ BOOLEAN jn, unbreakable_at_right = FALSE; \ debug0(DOF, DDD, "MoveRightToGap(I, x, rlink, right, -, -, -)"); \ \ /* search onwards to find newg, the next true breakpoint */ \ /* *** \ Child(tmp, rlink); \ debug2(DOF, DDD, "NextDefiniteWithGap(%s, %s)", EchoObject(x), \ EchoObject(tmp)); \ *** */ \ NextDefiniteWithGap(x, rlink, foll, newg, jn); \ \ /* set right link and calculate badness of the new interval */ \ if( rlink != x ) \ { \ assert( Up(newg) == LastUp(newg), "MoveRightToGap: newg!" ); \ /* set save_space(newg) now so that it is OK to forget right */ \ debug0(DOF, DDD, " MoveRightToGap setting save_space(newg)"); \ if( I.cwid != nilobj ) etc_width = bfc(constraint(I.cwid)); \ if( mode(gap(newg)) == TAB_MODE ) \ { save_space(newg) = ActualGap(0, back(foll,COLM), fwd(foll,COLM), \ &gap(newg), etc_width, 0) - back(foll, COLM); \ } \ else \ { save_space(newg) = ActualGap(fwd(right, COLM), back(foll, COLM), \ fwd(foll,COLM), &gap(newg), etc_width, \ I.nat_width - fwd(right,COLM)) \ - back(foll, COLM) - fwd(right, COLM); \ } \ \ ifdebug(DOF, DDD, \ if( Down(newg) != newg ) \ { OBJECT tmp; \ Child(tmp, Down(newg)); \ debug5(DOF, DDD, "newg %s: %s %s, gap = %s, save_space = %s", \ Image(type(newg)), Image(type(tmp)), EchoObject(tmp), \ EchoGap(&gap(newg)), EchoLength(save_space(newg))); \ } \ else debug3(DOF, DDD, "newg %s: gap = %s, save_space = %s", \ Image(type(newg)), EchoGap(&gap(newg)), \ EchoLength(save_space(newg))); \ ) \ \ /* sort out ending with hyphenation and/or being unbreakable */ \ /* NB ADD_HYPH is possible after a restart */ \ if( mode(gap(newg)) == HYPH_MODE || mode(gap(newg)) == ADD_HYPH ) \ { if( hyph_allowed ) \ { \ /* hyphenation is allowed, so add hyph_word to nat_width */ \ if( is_word(type(right)) ) \ { \ ch = string(right)[StringLength(string(right))-1]; \ if( ch != CH_HYPHEN && ch != CH_SLASH ) \ { \ /* make sure hyph_word exists and is of the right font */ \ debug0(DOF, DDD, " MoveRightToGap checking hyph_word"); \ if( hyph_word == nilobj ) \ { hyph_word = MakeWord(WORD, STR_HYPHEN, &fpos(x)); \ word_font(hyph_word) = 0; \ word_colour(hyph_word) = colour(save_style(x)); \ word_underline_colour(hyph_word)=underline_colour(save_style(x));\ word_texture(hyph_word) = texture(save_style(x)); \ word_outline(hyph_word) = outline(save_style(x)); \ word_language(hyph_word) = language(save_style(x)); \ word_baselinemark(hyph_word) = baselinemark(save_style(x));\ word_strut(hyph_word) = strut(save_style(x)); \ word_ligatures(hyph_word) = ligatures(save_style(x)); \ word_hyph(hyph_word) = hyph_style(save_style(x))==HYPH_ON;\ } \ if( word_font(hyph_word) != word_font(right) ) \ { word_font(hyph_word) = word_font(right); \ FposCopy(fpos(hyph_word), fpos(x)); \ FontWordSize(hyph_word); \ } \ \ mode(gap(newg)) = ADD_HYPH; \ if( !marginkerning(save_style(x)) ) \ I.nat_width += size(hyph_word, COLM); \ debug0(DOF, DDD, " adding hyph_word from nat_width"); \ } \ } \ } \ else \ { \ /* hyphenation is not allowed, so this gap is unbreakable */ \ unbreakable_at_right = TRUE; \ } \ } \ else if( nobreak(gap(newg)) ) \ unbreakable_at_right = TRUE; \ \ I.rlink = Up(newg); \ debug2(DOF, DDD, " MoveRightToGap setting I.rlink to %s %s", \ Image(type(newg)), EchoObject(newg)); \ } \ else I.rlink = x; \ SetIntervalBadness(I, max_width, etc_width); \ if( unbreakable_at_right ) I.class = UNBREAKABLE_RIGHT; \ else if( I.class == TIGHT && mode(gap(newg)) == TAB_MODE ) \ I.class = TOO_TIGHT, I.badness = TOO_TIGHT_BAD; \ debug0(DOF, DDD, "MoveRightToGap returning."); \ } /*@::IntervalInit(), IntervalShiftRightEnd()@*********************************/ /* */ /* IntervalInit(I, x, max_width, etc_width, hyph_word) */ /* */ /* Set I to the first interval of x. */ /* */ /*****************************************************************************/ #define IntervalInit(I, x, max_width, etc_width, hyph_word) \ { OBJECT rlink, right = nilobj; BOOLEAN jn; \ debug0(DOF, DDD, "IntervalInit(I, x, -, -, hyph_word)"); \ I.badness = 0; /* added by JeffK 17/6/23 */ \ I.llink = x; \ \ FirstDefinite(x, rlink, right, jn); \ if( rlink == x ) I.class = AT_END, I.rlink = x; \ else \ { \ /* have first definite object, so set interval width etc. */ \ if( multi != nilobj ) \ { Child(I.cwid, Down(multi)); \ } \ else I.cwid = nilobj; \ I.nat_width = size(right, COLM); \ I.space_width = 0; \ I.tab_count = 0; \ I.width_to_tab = 0; /* in fact undefined, added by JeffK 17/6/23 */ \ I.tab_pos = 0; /* in fact undefined, added by JeffK 17/6/23 */ \ \ /* move to gap, check hyphenation there etc. */ \ MoveRightToGap(I,x,rlink,right,max_width,etc_width,hyph_word); \ } \ debug0(DOF, DDD, "IntervalInit returning."); \ } /* end macro IntervalInit */ /*****************************************************************************/ /* */ /* IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width) */ /* */ /* Shift the right end of interval I one place to the right. */ /* */ /*****************************************************************************/ #define IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width) \ { OBJECT rlink, g, right = nilobj; \ assert( I.class != AT_END, "IntervalShiftRightEnd: AT_END!" ); \ rlink = I.rlink; \ if( rlink == x ) I.class = AT_END; \ else \ { \ /* I is optimal here so save its badness and left endpoint */ \ Child(g, rlink); \ assert( type(g) == GAP_OBJ, "IntervalShiftRightEnd: type(g)!" ); \ save_badness(g) = I.badness; \ save_prev(g) = I.llink; \ save_cwid(g) = I.cwid; \ \ /* if hyphenation case, must take away width of hyph_word */ \ /* and increase the badness to discourage breaks at this point */ \ if( mode(gap(g)) == ADD_HYPH ) \ { \ if( !marginkerning(save_style(x)) ) \ I.nat_width -= size(hyph_word,COLM); \ save_badness(g) += HYPH_BAD_INCR; \ debug0(DOF, DDD, " subtracting hyph_word from nat_width"); \ } \ \ /* find definite object which must lie just to the right of g */ \ NextDefinite(x, rlink, right); \ assert( rlink != x, "IntervalShiftRightEnd: rlink == x!" ); \ \ /* modify I to reflect the addition of g and right */ \ if( mode(gap(g)) == TAB_MODE ) \ { I.tab_count++; \ I.tab_pos = save_space(g); \ I.width_to_tab = I.nat_width; \ I.nat_width = save_space(g) + size(right, COLM); \ I.space_width = 0; \ } \ else \ { I.nat_width += save_space(g) + size(right, COLM); \ I.space_width += save_space(g); \ } \ \ /* now shift one step to the right */ \ MoveRightToGap(I, x, rlink, right, max_width, etc_width,hyph_word); \ } \ } /* end macro IntervalShiftRightEnd */ /*@::IntervalShiftLeftEnd(), IntervalBadness()@*******************************/ /* */ /* IntervalShiftLeftEnd(I, x, max_width, etc_width) */ /* */ /* Shift the left end of interval I one place to the right. */ /* */ /*****************************************************************************/ #define IntervalShiftLeftEnd(I, x, max_width, etc_width) \ { OBJECT llink, left = nilobj, lgap, y; BOOLEAN jn; \ debug1(DOF, DDD, "IntervalShiftLeftEnd(%s)", IntervalPrint(I, x)); \ assert( I.class != AT_END, "IntervalShiftLeftEnd: AT_END!" ); \ \ /* find left, the leftmost definite object of I */ \ llink = I.llink; \ NextDefinite(x, llink, left); \ assert( llink != x, "IntervalShiftLeftEnd: llink == x!" ); \ \ /* find lgap, the first true breakpoint following left */ \ NextDefiniteWithGap(x, llink, y, lgap, jn); \ assert( llink != x, "IntervalShiftLeftEnd: llink == x!" ); \ \ /* calculate width and badness of interval minus left and lgap */ \ if( mode(gap(lgap)) == TAB_MODE ) \ { assert( I.tab_count > 0 || Up(lgap) == I.rlink, \ "IntervalShiftLeftEnd: tab_count <= 0!" ); \ I.tab_count--; \ if( I.tab_count == 0 ) I.nat_width -= save_space(lgap); \ } \ else /* take from nat_width, or if tab, from width_to_tab */ \ { if( I.tab_count == 0 ) \ { I.nat_width -= save_space(lgap) + size(left, COLM); \ I.space_width -= save_space(lgap); \ } \ else if( I.tab_count == 1 ) \ { I.width_to_tab -= save_space(lgap) + size(left, COLM); \ } \ /* else no changes since tabs hide them */ \ } \ I.llink = Up(lgap); \ if( I.llink == I.rlink ) \ { I.class = EMPTY_INTERVAL; \ I.badness = TOO_TIGHT_BAD + 1; \ } \ else \ { \ if( save_cwid(lgap) != nilobj ) \ { OBJECT tlink; \ tlink = NextDown(Up(save_cwid(lgap))); \ if( type(tlink) == ACAT ) I.cwid = save_cwid(lgap); \ else Child(I.cwid, tlink); \ } \ SetIntervalBadness(I, max_width, etc_width); \ if( nobreak(gap(lgap)) || ( !hyph_allowed && \ (mode(gap(lgap))==HYPH_MODE || mode(gap(lgap))==ADD_HYPH) ) ) \ I.class = UNBREAKABLE_LEFT; \ } \ debug1(DOF, DDD, "IShiftLeftEnd returning %s", IntervalPrint(I, x)); \ } /* end macro IntervalShiftLeftEnd */ /*****************************************************************************/ /* */ /* IntervalBadness(I) */ /* */ /* Return the badness of interval I. */ /* */ /*****************************************************************************/ #define IntervalBadness(I) (I.badness) /*@IntervalClass(), IntervalPrint()@******************************************/ /* */ /* IntervalClass(I) */ /* */ /* Return the badness class of interval I. */ /* */ /*****************************************************************************/ #define IntervalClass(I) (I.class) #if DEBUG_ON /*****************************************************************************/ /* */ /* IntervalPrint(I, x) */ /* */ /* Return string image of the contents of interval I of ACAT x. */ /* */ /*****************************************************************************/ static FULL_CHAR *IntervalPrint(INTERVAL I, OBJECT x) { static char *class_name[] = { "TOO_LOOSE", "LOOSE", "TIGHT", "TOO_TIGHT", "TAB_OVERLAP", "AT_END", "UNBREAKABLE_LEFT", "UNBREAKABLE_RIGHT" }; OBJECT link, y, g, z; int i; static FULL_CHAR res[300]; if( I.llink == I.rlink ) return AsciiToFull("[]"); StringCopy(res, AsciiToFull("")); if( I.cwid != nilobj ) { StringCat(res, AsciiToFull("!")); StringCat(res, EchoLength(bfc(constraint(I.cwid)))); StringCat(res, AsciiToFull("!")); } StringCat(res, AsciiToFull("[")); g = nilobj; for( link = NextDown(I.llink); link != I.rlink; link = NextDown(link) ) { assert(link != x, "IntervalPrint: link == x!"); Child(y, link); debug2(DOF, DDD, "IntervalPrint at %s %s", Image(type(y)), EchoObject(y)); assert(y != x, "IntervalPrint: y == x!"); if( type(y) == GAP_OBJ ) { g = y; if( Down(g) != g ) { Child(z, Down(g)); StringCat(res, STR_SPACE); StringCat(res, EchoCatOp(ACAT, mark(gap(g)), join(gap(g)))), StringCat(res, is_word(type(z)) ? string(z) : Image(type(z))); StringCat(res, STR_SPACE); } else for( i = 1; i <= hspace(g) + vspace(g); i++ ) StringCat(res, STR_SPACE); } else if( is_word(type(y)) ) StringCat(res, string(y)[0] == '\0' ? AsciiToFull("{}") : string(y)); else StringCat(res, Image(type(y))); } StringCat(res, AsciiToFull("] n")); StringCat(res, EchoLength(I.nat_width)); StringCat(res, AsciiToFull(", ")); StringCat(res, EchoLength(I.space_width)); StringCat(res, AsciiToFull(" (")); StringCat(res, AsciiToFull(class_name[I.class])); StringCat(res, AsciiToFull(" ")); StringCat(res, StringInt(I.badness)); StringCat(res, AsciiToFull(")")); if( I.tab_count > 0 ) { StringCat(res, AsciiToFull(" <")); StringCat(res, StringInt(I.tab_count)); StringCat(res, STR_SPACE); StringCat(res, EchoLength(I.width_to_tab)); StringCat(res, AsciiToFull(":")); StringCat(res, EchoLength(I.tab_pos)); StringCat(res, AsciiToFull(">")); } return res; } /* end IntervalPrint */ #endif /*****************************************************************************/ /* */ /* BOOLEAN SmallGlyphHeight(FONT_NUM fnum, FULL_CHAR chr) */ /* */ /* Part of margin kerning, contributed by Ludovic Courtes. */ /* */ /* Return true if CHAR's glyph height in font FONTNUM is "small". A */ /* glyph's height is considered small when it is lower than or equal to */ /* three fourth of the height of the glyph for `x'. Initially, I */ /* considered that anything strictly smaller than `x' would be enough, */ /* but some fonts (namely Palatino) have a glyph for `v' which is */ /* slightly smaller than that for `x', hence weird results. */ /* */ /*****************************************************************************/ #define SmallGlyphHeight(fnum, chr) \ (FontGlyphHeight((fnum), (chr)) <= (3*(FontGlyphHeight((fnum), 'x') >> 2))) /*@KernWordLeftMargin ()@*****************************************************/ /* */ /* Part of margin kerning, contributed by Ludovic Courtes */ /* */ /* Perform left margin kerning of word FIRST_ON_LINE, whose parent is */ /* PARENT. If FIRST_ON_LINE's first glyph(s) deserve margin kerning, then */ /* a new word object containing this/these glyph(s) is prepended to */ /* FIRST_ON_LINE in PARENT. */ /* */ /*****************************************************************************/ static void KernWordLeftMargin(OBJECT first_on_line, OBJECT parent) { /* It's a word: look at its first characters' glyph height. */ FONT_NUM font; FULL_CHAR *word_content, *wordp; FULL_CHAR kerned_glyphs[20]; FULL_LENGTH kerned_glyphs_width = 0; unsigned kerned_glyph_count = 0, word_len; assert( is_word( type(first_on_line) ), "KernWordLeftMargin"); font = word_font(first_on_line); word_content = string(first_on_line); word_len = StringLength(word_content); if(font >= 1) { /* Determine how many subsequent characters beginning WORD_CONTENT deserve margin kerning. Glyphs wider than the glyph for `x' will _not_ be kerned at all, and only up to the width of the glyph for `x' can be protruded in other cases. */ FULL_LENGTH x_width = FontGlyphWidth(font, 'x'); for(wordp = word_content; (*wordp) && (SmallGlyphHeight(font, *wordp)); wordp++, kerned_glyph_count++) { FULL_LENGTH glyph_width; glyph_width = FontGlyphWidth(font, *wordp); if(kerned_glyphs_width + glyph_width > x_width) break; if(kerned_glyph_count >= sizeof(kerned_glyphs)) break; kerned_glyphs_width += glyph_width; kerned_glyphs[kerned_glyph_count] = *wordp; } if( kerned_glyph_count < 20 ) /* added by JeffK 17/6/2023 */ kerned_glyphs[kerned_glyph_count] = '\0'; } if(kerned_glyph_count > 0) { OBJECT z; FULL_CHAR *unacc = NULL; FULL_LENGTH glyph_width; debug2(DOF, DD, " margin-kerning %u glyph from " "word \"%s\" (left margin)", kerned_glyph_count, word_content); /* Get font information. */ if(finfo[font].font_table) { MAPPING m; m = font_mapping(finfo[font].font_table); unacc = MapTable[m]->map[MAP_UNACCENTED]; } /* Add the first characters. */ z = MakeWord(WORD, kerned_glyphs, &fpos(first_on_line)); word_font(z) = word_font(first_on_line); word_colour(z) = word_colour(first_on_line); word_underline_colour(z) = word_underline_colour(first_on_line); word_texture(z) = word_texture(first_on_line); word_outline(z) = word_outline(first_on_line); word_language(z) = word_language(first_on_line); word_baselinemark(z) = word_baselinemark(first_on_line); word_strut(z) = word_strut(first_on_line); word_ligatures(z) = word_ligatures(first_on_line); word_hyph(z) = hyph_style(save_style(z)) == HYPH_ON; underline(z) = underline(first_on_line); FontWordSize(z); /* Make it zero-width. FIXME: This awful trick allows Z to expand outside of the paragraph itself while still appearing as having a null width. */ glyph_width = size(z, COLM); back(z, COLM) = -glyph_width; fwd(z, COLM) = glyph_width; Link(parent, z); /* Add a zero-width gap object. */ New(z, GAP_OBJ); vspace(z) = 0; if ((word_content[kerned_glyph_count]) && (unacc)) hspace(z) = FontKernLength(font, unacc, word_content[kerned_glyph_count - 1], word_content[kerned_glyph_count]); else hspace(z) = 0; underline(z) = underline(first_on_line); SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0); Link(parent, z); /* Remove the first char from FIRST_ON_LINE and recompute its size. */ { unsigned s; for (s = 0; s < word_len - kerned_glyph_count; s++) word_content[s] = word_content[s + kerned_glyph_count]; word_content[word_len - kerned_glyph_count] = '\0'; } FontWordSize(first_on_line); } } /*@KernWordRightMargin ()@****************************************************/ /* */ /* Perform right margin kerning of word LAST_ON_LINE, whose parent is */ /* PARENT. If LAST_ON_LINE's first glyph(s) deserve margin kerning, then */ /* a new word object containing this/these glyph(s) is prepended to */ /* LAST_ON_LINE in PARENT. */ /* */ /*****************************************************************************/ static void KernWordRightMargin(OBJECT last_on_line, OBJECT parent) { FONT_NUM font; FULL_CHAR *word_content, *wordp; FULL_CHAR kerned_glyphs[20]; FULL_LENGTH kerned_glyphs_width = 0; unsigned kerned_glyph_count = 0, word_len; assert( is_word( type(last_on_line) ), "KernWordRightMargin"); font = word_font(last_on_line); word_content = string(last_on_line); word_len = StringLength(word_content); if(font >= 1) { /* Determine how many subsequent characters ending WORD_CONTENT deserve margin kerning. Glyphs wider than the glyph for `x' will _not_ be kerned at all, and only up to the width of the glyph for `x' can be protruded in other cases. */ FULL_LENGTH x_width = FontGlyphWidth(font, 'x'); for(wordp = &word_content[word_len - 1]; (wordp >= word_content) && (SmallGlyphHeight(font, *wordp)); wordp--, kerned_glyph_count++) { FULL_LENGTH glyph_width; glyph_width = FontGlyphWidth(font, *wordp); if(kerned_glyphs_width + glyph_width > x_width) break; if(kerned_glyph_count >= sizeof(kerned_glyphs)) break; kerned_glyphs_width += glyph_width; kerned_glyphs[kerned_glyph_count] = *wordp; } kerned_glyphs[kerned_glyph_count] = '\0'; } if(kerned_glyph_count > 0) { OBJECT z; FULL_CHAR *unacc = NULL; /* Get font information. */ if (finfo[font].font_table) { MAPPING m; m = font_mapping(finfo[font].font_table); unacc = MapTable[m]->map[MAP_UNACCENTED]; } debug2(DOF, DD, " margin-kerning %u glyph from " "word \"%s\" (right margin)", kerned_glyph_count, word_content); /* Add a zero-width gap object. */ New(z, GAP_OBJ); vspace(z) = 0; if((word_len > 1) && (unacc)) hspace(z) = FontKernLength(font, unacc, word_content[word_len - kerned_glyph_count - 1], word_content[word_len - kerned_glyph_count]); else hspace(z) = 0; underline(z) = underline(last_on_line); SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0); Link(parent, z); /* Add the last characters. */ z = MakeWord(WORD, &word_content[word_len - kerned_glyph_count], &fpos(last_on_line)); word_font(z) = word_font(last_on_line); word_colour(z) = word_colour(last_on_line); word_underline_colour(z) = word_underline_colour(last_on_line); word_texture(z) = word_texture(last_on_line); word_outline(z) = word_outline(last_on_line); word_language(z) = word_language(last_on_line); word_baselinemark(z) = word_baselinemark(last_on_line); word_strut(z) = word_strut(last_on_line); word_ligatures(z) = word_ligatures(last_on_line); word_hyph(z) = hyph_style(save_style(last_on_line)) == HYPH_ON; underline(z) = underline(last_on_line); FontWordSize(z); /* Make it zero-width. */ fwd(z, COLM) = 0; back(z, COLM) = 0; /* Remove the last char from LAST_ON_LINE and recompute its size. */ word_content[word_len - kerned_glyph_count] = '\0'; FontWordSize(last_on_line); Link(parent, z); } } /*@::FillObject()@************************************************************/ /* */ /* FillObject(x, c, multi, can_hyphenate, allow_shrink, extend_unbreakable, */ /* hyph_used) */ /* */ /* Break ACAT x into lines using optimal breakpoints. Set hyph_used to */ /* TRUE if any hyphenation was done. */ /* */ /* multi If multi is not nilobj, ignore c and use the */ /* sequence of constraints within multi for the */ /* successive lines. */ /* */ /* can_hyphenate TRUE if hyphenation is permitted during this fill. */ /* */ /* allow_shrink TRUE if gaps may be shrunk as well as expanded. */ /* */ /* extend_unbreakable TRUE if nobreak(gap()) fields are to be set so as */ /* to prevent gaps hidden under overstruck objects */ /* from becoming break points. */ /* */ /*****************************************************************************/ OBJECT FillObject(OBJECT x, CONSTRAINT *c, OBJECT multi, BOOLEAN can_hyphenate, BOOLEAN allow_shrink, BOOLEAN extend_unbreakable, BOOLEAN *hyph_used) { INTERVAL I, BestI; OBJECT res, gp, tmp, z = nilobj, y = nilobj, link, ylink, prev, next; int max_width, etc_width, outdent_margin = 0, f; BOOLEAN jn; unsigned typ; static OBJECT hyph_word = nilobj; BOOLEAN hyph_allowed; /* TRUE when hyphenation of words is permitted */ assert( type(x) == ACAT, "FillObject: type(x) != ACAT!" ); debug4(DOF, D, "FillObject(x, %s, can_hyph = %s, %s); %s", EchoConstraint(c), bool(can_hyphenate), multi == nilobj ? "nomulti" : "multi", EchoStyle(&save_style(x))); ifdebug(DOF, DD, DebugObject(x)); *hyph_used = FALSE; if( multi == nilobj ) { /* set max_width (width of 1st line), etc_width (width of later lines) */ max_width = find_min(fc(*c), bfc(*c)); if( display_style(save_style(x)) == DISPLAY_OUTDENT || display_style(save_style(x)) == DISPLAY_ORAGGED ) { /* outdent_margin = 2 * FontSize(font(save_style(x)), x); */ outdent_margin = outdent_len(save_style(x)); etc_width = max_width - outdent_margin; } else etc_width = max_width; assert( size(x, COLM) > max_width, "FillObject: initial size!" ); /* if column width is ridiculously small, exit with error message */ if( max_width <= 2 * FontSize(font(save_style(x)), x) ) { Error(14, 6, "paragraph deleted (assigned width %s is too narrow)", WARN, &fpos(x), EchoLength(max_width)); res = MakeWord(WORD, STR_EMPTY, &fpos(x)); word_font(res) = font(save_style(x)); word_colour(res) = colour(save_style(x)); word_underline_colour(res) = underline_colour(save_style(x)); word_texture(res) = texture(save_style(x)); word_outline(res) = outline(save_style(x)); word_language(res) = language(save_style(x)); word_baselinemark(res) = baselinemark(save_style(x)); word_strut(res) = strut(save_style(x)); word_ligatures(res) = ligatures(save_style(x)); word_hyph(res) = hyph_style(save_style(x)) == HYPH_ON; back(res, COLM) = fwd(res, COLM) = 0; ReplaceNode(res, x); DisposeObject(x); return res; } } else max_width = etc_width = 0; /* not used really */ /* add &1rt {} to end of paragraph */ New(gp, GAP_OBJ); hspace(gp) = 1; vspace(gp) = 0; SetGap(gap(gp), FALSE, FALSE, TRUE, AVAIL_UNIT, TAB_MODE, 1*FR); tmp = MakeWord(WORD, STR_GAP_RJUSTIFY, &fpos(x)); Link(gp, tmp); Link(x, gp); tmp = MakeWord(WORD, STR_EMPTY, &fpos(x)); back(tmp, COLM) = fwd(tmp, COLM) = back(tmp, ROWM) = fwd(tmp, ROWM) = 0; word_font(tmp) = 0; word_colour(tmp) = 0; word_underline_colour(tmp) = 0; word_texture(tmp) = 1; word_outline(tmp) = 0; word_language(tmp) = 0; word_baselinemark(tmp) = FALSE; word_strut(tmp) = FALSE; word_ligatures(tmp) = TRUE; word_hyph(tmp) = 0; underline(tmp) = UNDER_OFF; Link(x, tmp); /* if extend_unbreakable, run through x and set every gap in the */ /* shadow of a previous gap to be unbreakable */ if( extend_unbreakable ) { int f, max_f; OBJECT g; FirstDefinite(x, link, y, jn); assert( link != x, "FillObject/extend_unbreakable: link == x!" ); f = max_f = size(y, COLM); prev = y; NextDefiniteWithGap(x, link, y, g, jn); while( link != x ) { /* add unbreakableness if gap is overshadowed by a previous one */ f += MinGap(fwd(prev, COLM), back(y, COLM), fwd(y, COLM), &gap(g)) - fwd(prev, COLM) + back(y, COLM); if( f < max_f ) { if( units(gap(g)) == FIXED_UNIT ) nobreak(gap(g)) = TRUE; } else { max_f = f; } /* on to next component and gap */ prev = y; NextDefiniteWithGap(x, link, y, g, jn); } } /* initially we can hyphenate if hyphenation is on, but not first pass */ if( hyph_style(save_style(x)) == HYPH_UNDEF ) Error(14, 7, "hyphen or nohyphen option missing", FATAL, &fpos(x)); hyph_allowed = FALSE; /* initialize I to first interval, BestI to best ending here, and run */ RESTART: IntervalInit(I, x, max_width, etc_width, hyph_word); BestI = I; while( IntervalClass(I) != AT_END ) { debug0(DOF, DD, "loop:"); debug1(DOF, DD, " %s", IntervalPrint(I, x)); switch( IntervalClass(I) ) { case TOO_LOOSE: case EMPTY_INTERVAL: /* too loose, so save best and shift right end */ if( IntervalClass(I) == EMPTY_INTERVAL || IntervalBadness(BestI) <= IntervalBadness(I) ) I = BestI; debug1(DOF, DD, "BestI: %s", IntervalPrint(I, x)); debug0(DOF, DD, ""); /* NB no break */ case UNBREAKABLE_RIGHT: IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width); BestI = I; break; case LOOSE: case TIGHT: case TOO_TIGHT: /* reasonable, so check best and shift left end */ if( IntervalBadness(I) < IntervalBadness(BestI) ) BestI = I; /* NB no break */ case UNBREAKABLE_LEFT: case TAB_OVERLAP: /* too tight, or unbreakable gap at left end, so shift left end */ IntervalShiftLeftEnd(I, x, max_width, etc_width); break; /* *** case EMPTY_INTERVAL: PrevDefinite(x, I.llink, y); if( can_hyphenate ) { x = Hyphenate(x); can_hyphenate = FALSE; hyph_allowed = TRUE; *hyph_used = TRUE; } else CorrectOversize(x, I.llink, (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : etc_width); goto RESTART; *** */ default: assert(FALSE, "FillObject: IntervalClass(I)"); break; } } /* do end processing */ ifdebug(DOF, DD, debug0(DOF, DD, "final result:"); debug1(DOF, DD, "%s", IntervalPrint(BestI, x)); while( BestI.llink != x ) { BestI.rlink = BestI.llink; Child(gp, BestI.rlink); BestI.llink = save_prev(gp); debug1(DOF, DD, "%s", IntervalPrint(BestI, x)); } ); if( can_hyphenate && IntervalBadness(BestI) > HYPH_BAD ) { /* the result is bad enough to justify the cost of a second attempt, */ /* with hyphenation turned on this time */ x = Hyphenate(x); can_hyphenate = FALSE; hyph_allowed = TRUE; *hyph_used = TRUE; goto RESTART; } else if( I.llink == x ) { /* The result has only one line. Since the line did not fit initially, */ /* this must mean either that a large word was discarded or else that */ /* the line was only slightly tight */ if( multi == nilobj ) { res = x; back(res, COLM) = 0; fwd(res, COLM) = max_width; } else { New(res, VCAT); adjust_cat(res) = FALSE; ReplaceNode(res, x); Link(res, x); } } else { OBJECT lgap, llink; New(res, VCAT); adjust_cat(res) = FALSE; back(res, COLM) = 0; fwd(res, COLM) = max_width; ReplaceNode(res, x); llink = I.llink; /* break the lines of x */ while( llink != x ) { New(y, ACAT); adjust_cat(y) = adjust_cat(x); FposCopy(fpos(y), fpos(x)); StyleCopy(save_style(y), save_style(x)); if( Down(res) != res && (display_style(save_style(y)) == DISPLAY_ADJUST || display_style(save_style(y)) == DISPLAY_OUTDENT) ) display_style(save_style(y)) = DO_ADJUST; back(y, COLM) = 0; fwd(y, COLM) = max_width; if( marginkerning(save_style(x)) ) { /* Margin kerning: look at this line's first character. */ OBJECT first_on_line, parent; /* Get the first object on this line. */ parent = NextDown(llink); Child(first_on_line, parent); if( is_word( type(first_on_line) ) ) KernWordLeftMargin(first_on_line, parent); } /* if outdented paragraphs, add 2.0f @Wide & to front of new line */ if( display_style(save_style(x)) == DISPLAY_OUTDENT || display_style(save_style(x)) == DISPLAY_ORAGGED ) { OBJECT t1, t2, z; t1 = MakeWord(WORD, STR_EMPTY, &fpos(x)); back(t1, COLM) = fwd(t1, COLM) = back(t1, ROWM) = fwd(t1, ROWM) = 0; word_font(t1) = 0; word_colour(t1) = 0; word_underline_colour(t1) = 0; word_texture(t1) = 1; word_outline(t1) = 0; word_language(t1) = 0; word_baselinemark(t1) = FALSE; word_strut(t1) = FALSE; word_ligatures(t1) = TRUE; word_hyph(t1) = 0; underline(t1) = UNDER_OFF; New(t2, WIDE); SetConstraint(constraint(t2), MAX_FULL_LENGTH, outdent_margin, MAX_FULL_LENGTH); back(t2, COLM) = 0; fwd(t2, COLM) = outdent_margin; underline(t2) = UNDER_OFF; Link(t2, t1); Link(y, t2); New(z, GAP_OBJ); hspace(z) = vspace(z) = 0; SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0); Link(y, z); } /* move the line to below y */ TransferLinks(NextDown(llink), x, y); /* add hyphen to end of previous line, if lgap is ADD_HYPH */ Child(lgap, llink); if( mode(gap(lgap)) == ADD_HYPH ) { OBJECT z, tmp; /* FONT_NUM font; */ /* FULL_CHAR *unacc = NULL, *word_content */; /* unsigned word_len; */ /* find word hyphen attaches to, since need its underline and font */ Child(tmp, PrevDown(LastDown(x))); /* last is lgap, so one before */ debug2(DOF, D, "tmp = %s %s", Image(type(tmp)), EchoObject(tmp)); assert(is_word(type(tmp)), "FillObject: !is_word(type(tmp))!"); /* word_content = string(tmp); */ /* word_len = StringLength(word_content); */ /* get font information */ /* *** font = word_font(tmp); if (finfo[font].font_table) { MAPPING m; m = font_mapping(finfo[font].font_table); ** unacc = MapTable[m]->map[MAP_UNACCENTED]; ** } *** */ /* add zero-width gap object */ New(z, GAP_OBJ); debug0(DOF, DD, " adding hyphen"); debug0(DOF, DD, ""); hspace(z) = vspace(z) = 0; /* *** vspace(z) = 0; if (unacc) hspace(z) = FontKernLength(font, unacc, word_content[word_len - 1], CH_HYPHEN); else hspace(z) = 0; *** */ underline(z) = underline(tmp); SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, ADD_HYPH, 0); Link(x, z); /* add hyphen */ z = MakeWord(WORD, STR_HYPHEN, &fpos(y)); word_font(z) = word_font(tmp); word_colour(z) = word_colour(tmp); word_underline_colour(z) = word_underline_colour(tmp); word_texture(z) = word_texture(tmp); word_outline(z) = word_outline(tmp); word_language(z) = word_language(tmp); word_baselinemark(z) = word_baselinemark(tmp); word_strut(z) = word_strut(tmp); word_ligatures(z) = word_ligatures(tmp); word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON; underline(z) = underline(tmp); FontWordSize(z); if( marginkerning(save_style(x)) ) { /* Margin kerning: set the hyphen's width to zero. */ back(z, COLM) = 0; fwd(z, COLM) = 0; } Link(x, z); } else if( marginkerning(save_style(x)) ) { /* Margin kerning: look at the height of this line's last char. */ OBJECT last_on_line; /* Get the last object on this line. */ Child(last_on_line, PrevDown(LastDown(x))); if( is_word(type(last_on_line)) ) KernWordRightMargin(last_on_line, x); } /* attach y to res, recycle lgap for gap separating the two lines */ Link(NextDown(res), y); MoveLink(llink, NextDown(res), PARENT); hspace(lgap) = 0; vspace(lgap) = 1; GapCopy(gap(lgap), line_gap(save_style(x))); if( Down(lgap) != lgap ) DisposeChild(Down(lgap)); /* move on to previous line */ llink = save_prev(lgap); } /* attach first line, x, to res */ Link(NextDown(res), x); back(x, COLM) = 0; fwd(x, COLM) = max_width; if( display_style(save_style(x)) == DISPLAY_ADJUST || display_style(save_style(x)) == DISPLAY_OUTDENT ) display_style(save_style(x)) = DO_ADJUST; /* if last line contains only the {} from final &1rt {}, delete the line */ /* and the preceding gap */ Child(y, LastDown(res)); if( Down(y) == LastDown(y) ) { DisposeChild(LastDown(res)); assert( Down(res) != LastDown(res), "almost empty paragraph!" ); DisposeChild(LastDown(res)); } /* else delete the final &1rt {} from the last line, to help clines */ else { Child(z, LastDown(y)); assert( type(z)==WORD && string(z)[0]=='\0', "FillObject: last word!" ); DisposeChild(LastDown(y)); Child(z, LastDown(y)); assert( type(z) == GAP_OBJ, "FillObject: last gap_obj!" ); DisposeChild(LastDown(y)); } /* set unbreakable bit of first and last inter-line gaps, if required */ if( nobreakfirst(save_style(x)) && Down(res) != LastDown(res) ) { Child(gp, NextDown(Down(res))); assert( type(gp) == GAP_OBJ, "FillObject: type(gp) != GAP_OBJ (a)!" ); nobreak(gap(gp)) = TRUE; } if( nobreaklast(save_style(x)) && Down(res) != LastDown(res) ) { Child(gp, PrevDown(LastDown(res))); assert( type(gp) == GAP_OBJ, "FillObject: type(gp) != GAP_OBJ (b)!" ); nobreak(gap(gp)) = TRUE; } /* recalculate the width of the last line, since it may now be smaller */ assert( LastDown(res) != res, "FillObject: empty paragraph!" ); Child(y, LastDown(res)); FirstDefinite(y, link, z, jn); assert( link != y, "FillObject: last line is empty!" ); f = back(z, COLM); prev = z; NextDefiniteWithGap(y, link, z, gp, jn); while( link != y ) { f += MinGap(fwd(prev, COLM), back(z, COLM), fwd(z, COLM), &gap(gp)); prev = z; NextDefiniteWithGap(y, link, z, gp, jn); } fwd(y, COLM) = find_min(MAX_FULL_LENGTH, f + fwd(prev, COLM)); /* make last line DO_ADJUST if it is oversize */ if( size(y, COLM) > max_width ) display_style(save_style(y)) = DO_ADJUST; } /* rejoin unused hyphenated gaps so that kerning will work across them */ if( *hyph_used && type(res) == VCAT ) { for( link = Down(res); link != res; link = NextDown(link) ) { Child(y, link); if( type(y) == ACAT ) { for( ylink = Down(y); ylink != y; ylink = NextDown(ylink) ) { Child(gp, ylink); if( type(gp) == GAP_OBJ && width(gap(gp)) == 0 && mode(gap(gp)) == ADD_HYPH ) { /* possible candidate for joining, look into what's on each side */ Child(prev, PrevDown(ylink)); Child(next, NextDown(ylink)); if( is_word(type(prev)) && is_word(type(next)) && word_font(prev) == word_font(next) && word_colour(prev) == word_colour(next) && word_underline_colour(prev) == word_underline_colour(next) && word_texture(prev) == word_texture(next) && word_outline(prev) == word_outline(next) && word_language(prev) == word_language(next) && word_baselinemark(prev) == word_baselinemark(next) && word_strut(prev) == word_strut(next) && word_ligatures(prev) == word_ligatures(next) && underline(prev) == underline(next) ) { debug2(DOF, DD, "joining %s with %s", EchoObject(prev), EchoObject(next)); typ = type(prev) == QWORD || type(next) == QWORD ? QWORD : WORD; tmp = MakeWordTwo(typ, string(prev), string(next), &fpos(prev)); word_font(tmp) = word_font(prev); word_colour(tmp) = word_colour(prev); word_underline_colour(tmp) = word_underline_colour(prev); word_texture(tmp) = word_texture(prev); word_outline(tmp) = word_outline(prev); word_language(tmp) = word_language(prev); word_baselinemark(tmp) = word_baselinemark(prev); word_strut(tmp) = word_strut(prev); word_ligatures(tmp) = word_ligatures(prev); word_hyph(tmp) = word_hyph(prev); FontWordSize(tmp); underline(tmp) = underline(prev); MoveLink(ylink, tmp, CHILD); DisposeChild(Up(prev)); DisposeChild(Up(next)); } } } } } } debug0(DOF, D, "FillObject exiting"); return res; } /* end FillObject */