1 /*@z14.c:Fill Service:Declarations@*******************************************/
2 /*                                                                           */
3 /*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.39)                       */
4 /*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
5 /*                                                                           */
6 /*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
7 /*  School of Information Technologies                                       */
8 /*  The University of Sydney 2006                                            */
9 /*  AUSTRALIA                                                                */
10 /*                                                                           */
11 /*  This program is free software; you can redistribute it and/or modify     */
12 /*  it under the terms of the GNU General Public License as published by     */
13 /*  the Free Software Foundation; either Version 3, or (at your option)      */
14 /*  any later version.                                                       */
15 /*                                                                           */
16 /*  This program is distributed in the hope that it will be useful,          */
17 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
18 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
19 /*  GNU General Public License for more details.                             */
20 /*                                                                           */
21 /*  You should have received a copy of the GNU General Public License        */
22 /*  along with this program; if not, write to the Free Software              */
23 /*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
24 /*                                                                           */
25 /*  FILE:         z14.c                                                      */
26 /*  MODULE:       Fill Service                                               */
27 /*  EXTERNS:      FillObject()                                               */
28 /*                                                                           */
29 /*****************************************************************************/
30 #include "externs.h"
31 #define TOO_TIGHT_BAD	1048576	/* 2^21; badness of a too tight line         */
32 #define TOO_LOOSE_BAD	  65536	/* 2^16; the max badness of a too loose line */
33 #define	TIGHT_BAD	   4096	/* 2^12; the max badness of a tight line     */
34 #define	LOOSE_BAD	   4096	/* 2^12; the max badness of a loose line     */
35 #define	HYPH_BAD	    128	/* 2^ 7; threshold for calling hyphenation   */
36 #define	HYPH_BAD_INCR	     16	/* 2 ^4: the badness of one hyphen           */
37 #define	WIDOW_BAD_INCR	    128	/* 2 ^7: the badness of one widow word       */
38 #define SQRT_TOO_LOOSE	    512	/* 2^ 9; sqrt(TOO_LOOSE_BAD) (used to be)    */
39 #define	SQRT_TIGHT_BAD	    128	/* 2^ 7; sqrt(TIGHT_BAD) (used to be)        */
40 #define	SQRT_LOOSE_BAD	    128	/* 2^ 7; sqrt(LOOSE_BAD) (used to be)        */
41 #define	SQRT_TOO_TIGHT	   8192	/* 2^13; sqrt(TOO_TIGHT_BAD) (used to be)    */
42 #define MAX_EXPAND	1
43 #define MAX_SHRINK	4
44 
45 
46 typedef struct {
47   OBJECT	llink;		/* link to gap before left end of interval   */
48   OBJECT	rlink;		/* link to gap after right end of interval   */
49   OBJECT	cwid;		/* link to current line width in multi case  */
50   int		nat_width;	/* natural width of interval                 */
51   int		space_width;	/* natural width of spaces in the interval   */
52   int		badness;	/* badness of this interval		     */
53   unsigned char	class;		/* badness class of this interval	     */
54   unsigned char	tab_count;	/* number of gaps with tab mode in interval  */
55   int		tab_pos;	/* if tab_count > 0, this holds the position */
56 				/*  of the left edge of the object following */
57 				/*  the rightmost tab gap in the interval    */
58   int		width_to_tab;	/* if tab_count > 0, the interval width up   */
59 				/*  to but not including the rightmost tab   */
60 } INTERVAL;
61 
62 
63 /*****************************************************************************/
64 /*                                                                           */
65 /*  Badness classes                                                          */
66 /*                                                                           */
67 /*****************************************************************************/
68 
69 #define TOO_LOOSE	  0	/* interval is too loose		     */
70 #define LOOSE		  1	/* interval is loose but not too loose	     */
71 #define TIGHT		  2	/* interval is tight but not too tight	     */
72 #define TOO_TIGHT	  3	/* interval is too tight 		     */
73 #define TAB_OVERLAP	  4	/* interval has a tab and left part overlaps */
74 #define AT_END		  5	/* interval ends at right end of paragraph   */
75 #define UNBREAKABLE_LEFT  6	/* interval has an unbreakable gap at left   */
76 #define UNBREAKABLE_RIGHT 7	/* interval has an unbreakable gap at right  */
77 #define EMPTY_INTERVAL	  8	/* interval is empty                         */
78 
79 /*@::SetIntervalBadness()@****************************************************/
80 /*                                                                           */
81 /*  SetIntervalBadness(I)                                                    */
82 /*                                                                           */
83 /*  Private, calculates the badness and badness class of a non-empty         */
84 /*  interval.  Does not take into account any unbreakable gap at either end. */
85 /*                                                                           */
86 /*****************************************************************************/
87 
88 #define SetIntervalBadness(I, max_width, etc_width)			\
89 { OBJECT g; int badness;						\
90   int col_width;							\
91 									\
92   /* initialize to  saved badness of left-adjoining interval, if any */	\
93   /* and set width of column                                         */	\
94   if( I.llink == x )							\
95   { col_width = (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : max_width;	\
96     I.badness = 0;							\
97   }									\
98   else									\
99   { col_width = (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : etc_width;	\
100     Child(g, I.llink);							\
101     I.badness = save_badness(g);					\
102   }									\
103 									\
104   /* penalize widow lines, of the form [ <object> &1rt ... ] */		\
105   if( I.tab_count > 0 )							\
106   { OBJECT glink = NextDown(NextDown(I.llink));				\
107     assert( type(glink) == LINK, "SIB: glink!");			\
108     Child(g, glink);							\
109     if( type(g) == GAP_OBJ && mode(gap(g)) == TAB_MODE &&		\
110 	units(gap(g)) == AVAIL_UNIT && width(gap(g)) == 1*FR )		\
111       I.badness += WIDOW_BAD_INCR;					\
112   }									\
113 									\
114   if( col_width <= 0 )							\
115   { if( I.nat_width == 0 )						\
116     { I.class = TOO_LOOSE;						\
117       I.badness += 0;							\
118     }									\
119     else								\
120     { I.class = TIGHT;							\
121       I.badness += TOO_TIGHT_BAD;					\
122     }									\
123   }									\
124   else if( I.tab_count > 0 && I.width_to_tab > I.tab_pos )		\
125   { I.class = TAB_OVERLAP;						\
126     I.badness += TOO_TIGHT_BAD;						\
127   }									\
128   else if( MAX_EXPAND*(col_width-I.nat_width) > 2*I.space_width )	\
129   { I.class = I.tab_count > 0 ? LOOSE : TOO_LOOSE;			\
130     badness = (SQRT_TOO_LOOSE*(col_width - I.nat_width)) / col_width;	\
131     I.badness += badness * badness;					\
132   }									\
133   else if( I.nat_width <= col_width )					\
134   { I.class = LOOSE;							\
135     badness = (SQRT_LOOSE_BAD*(col_width - I.nat_width)) / col_width;	\
136     I.badness += badness * badness;					\
137   }									\
138   else if( BackEnd->fractional_spacing_avail && allow_shrink &&		\
139     MAX_SHRINK*(I.nat_width-col_width) <= I.space_width )		\
140   { I.class = TIGHT;							\
141     badness = (SQRT_TIGHT_BAD*(col_width - I.nat_width)) / col_width;	\
142     I.badness += badness * badness;					\
143   }									\
144   else									\
145   { I.class = TOO_TIGHT;						\
146     /***								\
147     badness = (SQRT_TOO_TIGHT*(col_width-I.nat_width)) / col_width;	\
148     I.badness += badness * badness;					\
149     ***/								\
150     I.badness += TOO_TIGHT_BAD;						\
151   }									\
152   assert( I.badness >= 0, "SetIntervalBadness: badness < 0!" );		\
153 } /* end macro SetIntervalBadness */
154 
155 
156 /*@::MoveRightToGap()@********************************************************/
157 /*                                                                           */
158 /*  MoveRightToGap(I, x, rlink, right, max_width, etc_width, hyph_word)      */
159 /*                                                                           */
160 /*  Private.  Shared by IntervalInit and IntervalShiftRightEnd, for moving   */
161 /*  to the next gap to the right, setting save_space(newg), checking for     */
162 /*  hyphenation case, and setting the interval badness.                      */
163 /*                                                                           */
164 /*****************************************************************************/
165 
166 #define MoveRightToGap(I,x,rlink,right,max_width,etc_width,hyph_word)	\
167 { OBJECT newg, foll = nilobj, tmp;  int ch;				\
168   BOOLEAN jn, unbreakable_at_right = FALSE;				\
169   debug0(DOF, DDD, "MoveRightToGap(I, x, rlink, right, -, -, -)");	\
170 									\
171   /* search onwards to find newg, the next true breakpoint */		\
172   Child(tmp, rlink);							\
173   debug2(DOF, DDD, "NextDefiniteWithGap(%s, %s)", EchoObject(x),	\
174     EchoObject(tmp));							\
175   NextDefiniteWithGap(x, rlink, foll, newg, jn);			\
176 									\
177   /* set right link and calculate badness of the new interval */	\
178   if( rlink != x )							\
179   { 									\
180     assert( Up(newg) == LastUp(newg), "MoveRightToGap: newg!" );	\
181     /* set save_space(newg) now so that it is OK to forget right */	\
182     debug0(DOF, DDD, "  MoveRightToGap setting save_space(newg)");	\
183     if( I.cwid != nilobj )  etc_width = bfc(constraint(I.cwid));	\
184     if( mode(gap(newg)) == TAB_MODE )					\
185     { save_space(newg) = ActualGap(0, back(foll,COLM), fwd(foll,COLM),	\
186 	  &gap(newg), etc_width, 0) - back(foll, COLM);			\
187     }									\
188     else								\
189     { save_space(newg) = ActualGap(fwd(right, COLM), back(foll, COLM),	\
190 	  fwd(foll,COLM), &gap(newg), etc_width,			\
191 	  I.nat_width - fwd(right,COLM))				\
192 	  - back(foll, COLM) - fwd(right, COLM);			\
193     }									\
194 									\
195     ifdebug(DOF, DDD,							\
196       if( Down(newg) != newg )						\
197       { OBJECT tmp;							\
198 	Child(tmp, Down(newg));						\
199 	debug5(DOF, DDD, "newg %s: %s %s, gap = %s, save_space = %s",	\
200 	Image(type(newg)), Image(type(tmp)), EchoObject(tmp),		\
201 	EchoGap(&gap(newg)), EchoLength(save_space(newg)));		\
202       }									\
203       else debug3(DOF, DDD, "newg %s: gap = %s, save_space = %s",	\
204 	Image(type(newg)), EchoGap(&gap(newg)),				\
205 	EchoLength(save_space(newg)));					\
206     )									\
207 									\
208     /* sort out ending with hyphenation and/or being unbreakable */	\
209     /* NB ADD_HYPH is possible after a restart                   */	\
210     if( mode(gap(newg)) == HYPH_MODE || mode(gap(newg)) == ADD_HYPH )	\
211     { if( hyph_allowed )						\
212       {									\
213 	/* hyphenation is allowed, so add hyph_word to nat_width */	\
214 	if( is_word(type(right)) )					\
215 	{								\
216 	  ch = string(right)[StringLength(string(right))-1];		\
217 	  if( ch != CH_HYPHEN && ch != CH_SLASH )			\
218 	  {								\
219 	    /* make sure hyph_word exists and is of the right font */	\
220 	    debug0(DOF, DDD, "  MoveRightToGap checking hyph_word");	\
221 	    if( hyph_word == nilobj )					\
222 	    { hyph_word = MakeWord(WORD, STR_HYPHEN, &fpos(x));		\
223 	      word_font(hyph_word) = 0;					\
224 	      word_colour(hyph_word) = colour(save_style(x));		\
225 	      word_underline_colour(hyph_word)=underline_colour(save_style(x));\
226 	      word_texture(hyph_word) = texture(save_style(x));		\
227 	      word_outline(hyph_word) = outline(save_style(x));		\
228 	      word_language(hyph_word) = language(save_style(x));	\
229 	      word_baselinemark(hyph_word) = baselinemark(save_style(x));\
230 	      word_strut(hyph_word) = strut(save_style(x));		\
231 	      word_ligatures(hyph_word) = ligatures(save_style(x));	\
232 	      word_hyph(hyph_word) = hyph_style(save_style(x))==HYPH_ON;\
233 	    }								\
234 	    if( word_font(hyph_word) != word_font(right) )		\
235 	    { word_font(hyph_word) = word_font(right);			\
236 	      FposCopy(fpos(hyph_word), fpos(x));			\
237 	      FontWordSize(hyph_word);					\
238 	    }								\
239 									\
240 	    mode(gap(newg)) = ADD_HYPH;					\
241 	    if( !marginkerning(save_style(x)) )				\
242 	      I.nat_width += size(hyph_word, COLM);			\
243 	    debug0(DOF, DDD, "   adding hyph_word from nat_width");	\
244 	  }								\
245         }								\
246       }									\
247       else								\
248       {									\
249 	/* hyphenation is not allowed, so this gap is unbreakable */	\
250 	unbreakable_at_right = TRUE;					\
251       }									\
252     }									\
253     else if( nobreak(gap(newg)) )					\
254       unbreakable_at_right = TRUE;					\
255 									\
256     I.rlink = Up(newg);							\
257     debug2(DOF, DDD, "  MoveRightToGap setting I.rlink to %s %s",	\
258       Image(type(newg)), EchoObject(newg));				\
259   }									\
260   else I.rlink = x;							\
261   SetIntervalBadness(I, max_width, etc_width);				\
262   if( unbreakable_at_right )  I.class = UNBREAKABLE_RIGHT;		\
263   else if( I.class == TIGHT && mode(gap(newg)) == TAB_MODE )		\
264     I.class = TOO_TIGHT, I.badness = TOO_TIGHT_BAD;			\
265   debug0(DOF, DDD, "MoveRightToGap returning.");				\
266 }
267 
268 /*@::IntervalInit(), IntervalShiftRightEnd()@*********************************/
269 /*                                                                           */
270 /*  IntervalInit(I, x, max_width, etc_width, hyph_word)                      */
271 /*                                                                           */
272 /*  Set I to the first interval of x.                                        */
273 /*                                                                           */
274 /*****************************************************************************/
275 
276 #define IntervalInit(I, x, max_width, etc_width, hyph_word)		\
277 { OBJECT rlink, right = nilobj; BOOLEAN jn;				\
278   debug0(DOF, DDD, "IntervalInit(I, x, -, -, hyph_word)");		\
279   I.llink = x;								\
280 									\
281   FirstDefinite(x, rlink, right, jn);					\
282   if( rlink == x )  I.class = AT_END, I.rlink = x;			\
283   else									\
284   { 									\
285     /* have first definite object, so set interval width etc. */	\
286     if( multi != nilobj )						\
287     { Child(I.cwid, Down(multi));					\
288     }									\
289     else I.cwid = nilobj;						\
290     I.nat_width = size(right, COLM);					\
291     I.space_width = 0;							\
292     I.tab_count = 0;							\
293 									\
294     /* move to gap, check hyphenation there etc. */			\
295     MoveRightToGap(I,x,rlink,right,max_width,etc_width,hyph_word); 	\
296   }									\
297   debug0(DOF, DDD, "IntervalInit returning.");				\
298 } /* end macro IntervalInit */
299 
300 
301 /*****************************************************************************/
302 /*                                                                           */
303 /*  IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width)             */
304 /*                                                                           */
305 /*  Shift the right end of interval I one place to the right.                */
306 /*                                                                           */
307 /*****************************************************************************/
308 
309 #define IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width) 	\
310 { OBJECT rlink, g, right = nilobj;					\
311   assert( I.class != AT_END, "IntervalShiftRightEnd: AT_END!" );	\
312   rlink = I.rlink;							\
313   if( rlink == x ) I.class = AT_END;					\
314   else									\
315   {									\
316     /* I is optimal here so save its badness and left endpoint */	\
317     Child(g, rlink);							\
318     assert( type(g) == GAP_OBJ, "IntervalShiftRightEnd: type(g)!" );	\
319     save_badness(g) = I.badness;					\
320     save_prev(g) = I.llink;						\
321     save_cwid(g) = I.cwid;						\
322 									\
323     /* if hyphenation case, must take away width of hyph_word */	\
324     /* and increase the badness to discourage breaks at this point */	\
325     if( mode(gap(g)) == ADD_HYPH )					\
326     {									\
327       if( !marginkerning(save_style(x)) )				\
328 	I.nat_width -= size(hyph_word,COLM);				\
329       save_badness(g) += HYPH_BAD_INCR;					\
330       debug0(DOF, DDD, "   subtracting hyph_word from nat_width");	\
331     }									\
332 									\
333     /* find definite object which must lie just to the right of g */	\
334     NextDefinite(x, rlink, right);					\
335     assert( rlink != x, "IntervalShiftRightEnd: rlink == x!" );		\
336 									\
337     /* modify I to reflect the addition of g and right */		\
338     if( mode(gap(g)) == TAB_MODE )					\
339     { I.tab_count++;							\
340       I.tab_pos = save_space(g);					\
341       I.width_to_tab = I.nat_width;					\
342       I.nat_width = save_space(g) + size(right, COLM);			\
343       I.space_width = 0;						\
344     }									\
345     else								\
346     { I.nat_width += save_space(g) + size(right, COLM);			\
347       I.space_width += save_space(g);					\
348     }									\
349 									\
350     /* now shift one step to the right */				\
351     MoveRightToGap(I, x, rlink, right, max_width, etc_width,hyph_word);	\
352   }									\
353 } /* end macro IntervalShiftRightEnd */
354 
355 
356 /*@::IntervalShiftLeftEnd(), IntervalBadness()@*******************************/
357 /*                                                                           */
358 /*  IntervalShiftLeftEnd(I, x, max_width, etc_width)                         */
359 /*                                                                           */
360 /*  Shift the left end of interval I one place to the right.                 */
361 /*                                                                           */
362 /*****************************************************************************/
363 
364 #define IntervalShiftLeftEnd(I, x, max_width, etc_width)		\
365 { OBJECT llink, left = nilobj, lgap, y;  BOOLEAN jn;			\
366   debug1(DOF, DDD, "IntervalShiftLeftEnd(%s)", IntervalPrint(I, x));	\
367   assert( I.class != AT_END, "IntervalShiftLeftEnd: AT_END!" );		\
368 									\
369   /* find left, the leftmost definite object of I */			\
370   llink = I.llink;							\
371   NextDefinite(x, llink, left);						\
372   assert( llink != x, "IntervalShiftLeftEnd: llink == x!" );		\
373 									\
374   /* find lgap, the first true breakpoint following left */		\
375   NextDefiniteWithGap(x, llink, y, lgap, jn);				\
376   assert( llink != x, "IntervalShiftLeftEnd: llink == x!" );		\
377 									\
378   /* calculate width and badness of interval minus left and lgap */	\
379   if( mode(gap(lgap)) == TAB_MODE )					\
380   { assert( I.tab_count > 0 || Up(lgap) == I.rlink,			\
381 			"IntervalShiftLeftEnd: tab_count <= 0!" );	\
382     I.tab_count--;							\
383     if( I.tab_count == 0 )  I.nat_width -= save_space(lgap);		\
384   }									\
385   else /* take from nat_width, or if tab, from width_to_tab */		\
386   { if( I.tab_count == 0 )						\
387     { I.nat_width -= save_space(lgap) + size(left, COLM);		\
388       I.space_width -= save_space(lgap);				\
389     }									\
390     else if( I.tab_count == 1 )						\
391     { I.width_to_tab -= save_space(lgap) + size(left, COLM);		\
392     }									\
393     /* else no changes since tabs hide them */				\
394   }									\
395   I.llink = Up(lgap);							\
396   if( I.llink == I.rlink )						\
397   { I.class = EMPTY_INTERVAL;						\
398     I.badness = TOO_TIGHT_BAD + 1;					\
399   }									\
400   else									\
401   {									\
402     if( save_cwid(lgap) != nilobj )					\
403     { OBJECT tlink;							\
404       tlink = NextDown(Up(save_cwid(lgap)));				\
405       if( type(tlink) == ACAT )  I.cwid = save_cwid(lgap);		\
406       else Child(I.cwid, tlink);					\
407     }									\
408     SetIntervalBadness(I, max_width, etc_width);			\
409     if( nobreak(gap(lgap)) || ( !hyph_allowed &&			\
410 	(mode(gap(lgap))==HYPH_MODE || mode(gap(lgap))==ADD_HYPH) ) )	\
411       I.class = UNBREAKABLE_LEFT;					\
412   }									\
413   debug1(DOF, DDD, "IShiftLeftEnd returning %s", IntervalPrint(I, x));	\
414 } /* end macro IntervalShiftLeftEnd */
415 
416 
417 /*****************************************************************************/
418 /*                                                                           */
419 /*  IntervalBadness(I)                                                       */
420 /*                                                                           */
421 /*  Return the badness of interval I.                                        */
422 /*                                                                           */
423 /*****************************************************************************/
424 
425 #define IntervalBadness(I)	(I.badness)
426 
427 
428 /*@IntervalClass(), IntervalPrint()@******************************************/
429 /*                                                                           */
430 /*  IntervalClass(I)                                                         */
431 /*                                                                           */
432 /*  Return the badness class of interval I.                                  */
433 /*                                                                           */
434 /*****************************************************************************/
435 
436 #define IntervalClass(I)	(I.class)
437 
438 
439 #if DEBUG_ON
440 /*****************************************************************************/
441 /*                                                                           */
442 /*  IntervalPrint(I, x)                                                      */
443 /*                                                                           */
444 /*  Return string image of the contents of interval I of ACAT x.             */
445 /*                                                                           */
446 /*****************************************************************************/
447 
IntervalPrint(INTERVAL I,OBJECT x)448 static FULL_CHAR *IntervalPrint(INTERVAL I, OBJECT x)
449 { static char *class_name[] =
450     { "TOO_LOOSE", "LOOSE", "TIGHT", "TOO_TIGHT", "TAB_OVERLAP", "AT_END",
451       "UNBREAKABLE_LEFT", "UNBREAKABLE_RIGHT" };
452   OBJECT link, y, g, z; int i;
453   static FULL_CHAR res[300];
454   if( I.llink == I.rlink )  return AsciiToFull("[]");
455   StringCopy(res, AsciiToFull(""));
456   if( I.cwid != nilobj )
457   { StringCat(res, AsciiToFull("!"));
458     StringCat(res, EchoLength(bfc(constraint(I.cwid))));
459     StringCat(res, AsciiToFull("!"));
460   }
461   StringCat(res, AsciiToFull("["));
462   g = nilobj;
463   for( link = NextDown(I.llink);  link != I.rlink;  link = NextDown(link) )
464   { assert(link != x, "IntervalPrint: link == x!");
465     Child(y, link);
466     debug2(DOF, DDD, "IntervalPrint at %s %s", Image(type(y)), EchoObject(y));
467     assert(y != x, "IntervalPrint: y == x!");
468     if( type(y) == GAP_OBJ )
469     { g = y;
470       if( Down(g) != g )
471       {	Child(z, Down(g));
472 	StringCat(res, STR_SPACE);
473 	StringCat(res, EchoCatOp(ACAT, mark(gap(g)), join(gap(g)))),
474 	StringCat(res, is_word(type(z)) ? string(z) : Image(type(z)));
475 	StringCat(res, STR_SPACE);
476       }
477       else for( i = 1;  i <= hspace(g) + vspace(g); i++ )
478 	     StringCat(res, STR_SPACE);
479     }
480     else if( is_word(type(y)) )
481 	StringCat(res, string(y)[0] == '\0' ? AsciiToFull("{}") : string(y));
482     else StringCat(res, Image(type(y)));
483   }
484   StringCat(res, AsciiToFull("] n"));
485   StringCat(res, EchoLength(I.nat_width));
486   StringCat(res, AsciiToFull(", "));
487   StringCat(res, EchoLength(I.space_width));
488   StringCat(res, AsciiToFull(" ("));
489   StringCat(res, AsciiToFull(class_name[I.class]));
490   StringCat(res, AsciiToFull(" "));
491   StringCat(res, StringInt(I.badness));
492   StringCat(res, AsciiToFull(")"));
493   if( I.tab_count > 0 )
494   { StringCat(res, AsciiToFull(" <"));
495     StringCat(res, StringInt(I.tab_count));
496     StringCat(res, STR_SPACE);
497     StringCat(res, EchoLength(I.width_to_tab));
498     StringCat(res, AsciiToFull(":"));
499     StringCat(res, EchoLength(I.tab_pos));
500     StringCat(res, AsciiToFull(">"));
501   }
502   return res;
503 } /* end IntervalPrint */
504 #endif
505 
506 /*****************************************************************************/
507 /*                                                                           */
508 /*  BOOLEAN SmallGlyphHeight(FONT_NUM fnum, FULL_CHAR chr)                   */
509 /*                                                                           */
510 /*  Part of margin kerning, contributed by Ludovic Courtes.                  */
511 /*                                                                           */
512 /*  Return true if CHAR's glyph height in font FONTNUM is "small".  A        */
513 /*  glyph's height is considered small when it is lower than or equal to     */
514 /*  three fourth of the height of the glyph for `x'.  Initially, I           */
515 /*  considered that anything strictly smaller than `x' would be enough,      */
516 /*  but some fonts (namely Palatino) have a glyph for `v' which is           */
517 /*  slightly smaller than that for `x', hence weird results.                 */
518 /*                                                                           */
519 /*****************************************************************************/
520 
521 #define SmallGlyphHeight(fnum, chr)			             \
522 (FontGlyphHeight((fnum), (chr)) <= (3*(FontGlyphHeight((fnum), 'x') >> 2)))
523 
524 
525 /*@KernWordLeftMargin ()@*****************************************************/
526 /*                                                                           */
527 /*  Part of margin kerning, contributed by Ludovic Courtes                   */
528 /*                                                                           */
529 /*  Perform left margin kerning of word FIRST_ON_LINE, whose parent is       */
530 /*  PARENT.  If FIRST_ON_LINE's first glyph(s) deserve margin kerning, then  */
531 /*  a new word object containing this/these glyph(s) is prepended to         */
532 /*  FIRST_ON_LINE in PARENT.                                                 */
533 /*                                                                           */
534 /*****************************************************************************/
535 
KernWordLeftMargin(OBJECT first_on_line,OBJECT parent)536 static void KernWordLeftMargin(OBJECT first_on_line, OBJECT parent)
537 {
538   /* It's a word: look at its first characters' glyph height.  */
539   FONT_NUM font;
540   FULL_CHAR *word_content, *wordp;
541   FULL_CHAR kerned_glyphs[20];
542   FULL_LENGTH kerned_glyphs_width = 0;
543   unsigned kerned_glyph_count = 0, word_len;
544 
545   assert( is_word( type(first_on_line) ), "KernWordLeftMargin");
546 
547   font = word_font(first_on_line);
548   word_content = string(first_on_line);
549   word_len = StringLength(word_content);
550 
551   if(font >= 1)
552   {
553     /* Determine how many subsequent characters beginning WORD_CONTENT
554        deserve margin kerning.  Glyphs wider than the glyph for `x' will
555        _not_ be kerned at all, and only up to the width of the glyph for `x'
556        can be protruded in other cases.  */
557     FULL_LENGTH x_width = FontGlyphWidth(font, 'x');
558 
559     for(wordp = word_content;
560        (*wordp) && (SmallGlyphHeight(font, *wordp));
561        wordp++, kerned_glyph_count++)
562     {
563       FULL_LENGTH glyph_width;
564 
565       glyph_width = FontGlyphWidth(font, *wordp);
566       if(kerned_glyphs_width + glyph_width > x_width)
567        break;
568       if(kerned_glyph_count >= sizeof(kerned_glyphs))
569        break;
570 
571       kerned_glyphs_width += glyph_width;
572       kerned_glyphs[kerned_glyph_count] = *wordp;
573     }
574 
575     kerned_glyphs[kerned_glyph_count] = '\0';
576   }
577 
578   if(kerned_glyph_count > 0)
579   { OBJECT z;
580     FULL_CHAR *unacc = NULL;
581     FULL_LENGTH glyph_width;
582 
583     debug2(DOF, DD, "   margin-kerning %u glyph from "
584           "word \"%s\" (left margin)",
585           kerned_glyph_count, word_content);
586 
587     /* Get font information.  */
588     if(finfo[font].font_table)
589     {
590       MAPPING m;
591       m = font_mapping(finfo[font].font_table);
592       unacc = MapTable[m]->map[MAP_UNACCENTED];
593     }
594 
595     /* Add the first characters.  */
596     z = MakeWord(WORD, kerned_glyphs, &fpos(first_on_line));
597     word_font(z) = word_font(first_on_line);
598     word_colour(z) = word_colour(first_on_line);
599     word_underline_colour(z) = word_underline_colour(first_on_line);
600     word_texture(z) = word_texture(first_on_line);
601     word_outline(z) = word_outline(first_on_line);
602     word_language(z) = word_language(first_on_line);
603     word_baselinemark(z) = word_baselinemark(first_on_line);
604     word_strut(z) = word_strut(first_on_line);
605     word_ligatures(z) = word_ligatures(first_on_line);
606     word_hyph(z) = hyph_style(save_style(z)) == HYPH_ON;
607     underline(z) = underline(first_on_line);
608     FontWordSize(z);
609 
610     /* Make it zero-width.
611        FIXME:  This awful trick allows Z to expand outside of the
612        paragraph itself while still appearing as having a null
613        width.  */
614     glyph_width = size(z, COLM);
615     back(z, COLM) = -glyph_width;
616     fwd(z, COLM) = glyph_width;
617     Link(parent, z);
618 
619     /* Add a zero-width gap object.  */
620     New(z, GAP_OBJ);
621     vspace(z) = 0;
622     if ((word_content[kerned_glyph_count]) && (unacc))
623       hspace(z) = FontKernLength(font, unacc,
624                                 word_content[kerned_glyph_count - 1],
625                                 word_content[kerned_glyph_count]);
626     else
627       hspace(z) = 0;
628     underline(z) = underline(first_on_line);
629     SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
630     Link(parent, z);
631 
632     /* Remove the first char from FIRST_ON_LINE and recompute its size.  */
633     {
634       unsigned s;
635       for (s = 0; s < word_len - kerned_glyph_count; s++)
636        word_content[s] = word_content[s + kerned_glyph_count];
637       word_content[word_len - kerned_glyph_count] = '\0';
638     }
639     FontWordSize(first_on_line);
640   }
641 }
642 
643 
644 /*@KernWordRightMargin ()@****************************************************/
645 /*                                                                           */
646 /*  Perform right margin kerning of word LAST_ON_LINE, whose parent is       */
647 /*  PARENT.  If LAST_ON_LINE's first glyph(s) deserve margin kerning, then   */
648 /*  a new word object containing this/these glyph(s) is prepended to         */
649 /*  LAST_ON_LINE in PARENT.                                                  */
650 /*                                                                           */
651 /*****************************************************************************/
652 
KernWordRightMargin(OBJECT last_on_line,OBJECT parent)653 static void KernWordRightMargin(OBJECT last_on_line, OBJECT parent)
654 {
655   FONT_NUM  font;
656   FULL_CHAR *word_content, *wordp;
657   FULL_CHAR kerned_glyphs[20];
658   FULL_LENGTH kerned_glyphs_width = 0;
659   unsigned kerned_glyph_count = 0, word_len;
660 
661   assert( is_word( type(last_on_line) ), "KernWordRightMargin");
662 
663   font = word_font(last_on_line);
664   word_content = string(last_on_line);
665   word_len = StringLength(word_content);
666 
667   if(font >= 1)
668   {
669     /* Determine how many subsequent characters ending WORD_CONTENT deserve
670        margin kerning.  Glyphs wider than the glyph for `x' will _not_ be
671        kerned at all, and only up to the width of the glyph for `x' can be
672        protruded in other cases.  */
673     FULL_LENGTH x_width = FontGlyphWidth(font, 'x');
674 
675     for(wordp = &word_content[word_len - 1];
676        (wordp >= word_content) && (SmallGlyphHeight(font, *wordp));
677        wordp--, kerned_glyph_count++)
678     {
679       FULL_LENGTH glyph_width;
680 
681       glyph_width = FontGlyphWidth(font, *wordp);
682       if(kerned_glyphs_width + glyph_width > x_width)
683        break;
684       if(kerned_glyph_count >= sizeof(kerned_glyphs))
685        break;
686 
687       kerned_glyphs_width += glyph_width;
688       kerned_glyphs[kerned_glyph_count] = *wordp;
689     }
690 
691     kerned_glyphs[kerned_glyph_count] = '\0';
692   }
693 
694   if(kerned_glyph_count > 0)
695   { OBJECT z;
696     FULL_CHAR *unacc = NULL;
697 
698     /* Get font information.  */
699     if (finfo[font].font_table)
700     {
701       MAPPING m;
702       m = font_mapping(finfo[font].font_table);
703       unacc = MapTable[m]->map[MAP_UNACCENTED];
704     }
705 
706     debug2(DOF, DD, "   margin-kerning %u glyph from "
707           "word \"%s\" (right margin)",
708           kerned_glyph_count, word_content);
709 
710     /* Add a zero-width gap object.  */
711     New(z, GAP_OBJ);
712     vspace(z) = 0;
713     if((word_len > 1) && (unacc))
714       hspace(z) = FontKernLength(font, unacc,
715                                 word_content[word_len - kerned_glyph_count - 1],
716                                 word_content[word_len - kerned_glyph_count]);
717     else
718       hspace(z) = 0;
719     underline(z) = underline(last_on_line);
720     SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
721     Link(parent, z);
722 
723     /* Add the last characters.  */
724     z = MakeWord(WORD, &word_content[word_len - kerned_glyph_count],
725                 &fpos(last_on_line));
726     word_font(z) = word_font(last_on_line);
727     word_colour(z) = word_colour(last_on_line);
728     word_underline_colour(z) = word_underline_colour(last_on_line);
729     word_texture(z) = word_texture(last_on_line);
730     word_outline(z) = word_outline(last_on_line);
731     word_language(z) = word_language(last_on_line);
732     word_baselinemark(z) = word_baselinemark(last_on_line);
733     word_strut(z) = word_strut(last_on_line);
734     word_ligatures(z) = word_ligatures(last_on_line);
735     word_hyph(z) = hyph_style(save_style(last_on_line)) == HYPH_ON;
736     underline(z) = underline(last_on_line);
737 
738     FontWordSize(z);
739 
740     /* Make it zero-width.  */
741     fwd(z, COLM) = 0;
742     back(z, COLM) = 0;
743 
744     /* Remove the last char from LAST_ON_LINE and recompute its size.  */
745     word_content[word_len - kerned_glyph_count] = '\0';
746     FontWordSize(last_on_line);
747 
748     Link(parent, z);
749   }
750 }
751 
752 
753 /*@::FillObject()@************************************************************/
754 /*                                                                           */
755 /*  FillObject(x, c, multi, can_hyphenate, allow_shrink, extend_unbreakable, */
756 /*                                              hyph_used)                   */
757 /*                                                                           */
758 /*  Break ACAT x into lines using optimal breakpoints.  Set hyph_used to     */
759 /*  TRUE if any hyphenation was done.                                        */
760 /*                                                                           */
761 /*    multi               If multi is not nilobj, ignore c and use the       */
762 /*                        sequence of constraints within multi for the       */
763 /*                        successive lines.                                  */
764 /*                                                                           */
765 /*    can_hyphenate       TRUE if hyphenation is permitted during this fill. */
766 /*                                                                           */
767 /*    allow_shrink        TRUE if gaps may be shrunk as well as expanded.    */
768 /*                                                                           */
769 /*    extend_unbreakable  TRUE if nobreak(gap()) fields are to be set so as  */
770 /*                        to prevent gaps hidden under overstruck objects    */
771 /*                        from becoming break points.                        */
772 /*                                                                           */
773 /*****************************************************************************/
774 
FillObject(OBJECT x,CONSTRAINT * c,OBJECT multi,BOOLEAN can_hyphenate,BOOLEAN allow_shrink,BOOLEAN extend_unbreakable,BOOLEAN * hyph_used)775 OBJECT FillObject(OBJECT x, CONSTRAINT *c, OBJECT multi, BOOLEAN can_hyphenate,
776   BOOLEAN allow_shrink, BOOLEAN extend_unbreakable, BOOLEAN *hyph_used)
777 { INTERVAL I, BestI;
778   OBJECT res, gp, tmp, z = nilobj, y = nilobj, link, ylink, prev, next;
779   int max_width, etc_width, outdent_margin = 0, f;  BOOLEAN jn;  unsigned typ;
780   static OBJECT hyph_word = nilobj;
781   BOOLEAN hyph_allowed;	    /* TRUE when hyphenation of words is permitted  */
782   assert( type(x) == ACAT, "FillObject: type(x) != ACAT!" );
783 
784   debug4(DOF, D, "FillObject(x, %s, can_hyph = %s, %s); %s",
785     EchoConstraint(c), bool(can_hyphenate),
786     multi == nilobj ? "nomulti" : "multi", EchoStyle(&save_style(x)));
787   ifdebug(DOF, DD, DebugObject(x));
788 
789   *hyph_used = FALSE;
790 
791   if( multi == nilobj )
792   {
793     /* set max_width (width of 1st line), etc_width (width of later lines) */
794     max_width = find_min(fc(*c), bfc(*c));
795     if( display_style(save_style(x)) == DISPLAY_OUTDENT ||
796         display_style(save_style(x)) == DISPLAY_ORAGGED )
797     {
798       /* outdent_margin = 2 * FontSize(font(save_style(x)), x); */
799       outdent_margin = outdent_len(save_style(x));
800       etc_width = max_width - outdent_margin;
801     }
802     else etc_width = max_width;
803     assert( size(x, COLM) > max_width, "FillObject: initial size!" );
804 
805     /* if column width is ridiculously small, exit with error message */
806     if( max_width <= 2 * FontSize(font(save_style(x)), x) )
807     {
808       Error(14, 6, "paragraph deleted (assigned width %s is too narrow)",
809          WARN, &fpos(x), EchoLength(max_width));
810       res = MakeWord(WORD, STR_EMPTY, &fpos(x));
811       word_font(res) = font(save_style(x));
812       word_colour(res) = colour(save_style(x));
813       word_underline_colour(res) = underline_colour(save_style(x));
814       word_texture(res) = texture(save_style(x));
815       word_outline(res) = outline(save_style(x));
816       word_language(res) = language(save_style(x));
817       word_baselinemark(res) = baselinemark(save_style(x));
818       word_strut(res) = strut(save_style(x));
819       word_ligatures(res) = ligatures(save_style(x));
820       word_hyph(res) = hyph_style(save_style(x)) == HYPH_ON;
821       back(res, COLM) = fwd(res, COLM) = 0;
822       ReplaceNode(res, x);
823       DisposeObject(x);
824       return res;
825     }
826   }
827   else max_width = etc_width = 0; /* not used really */
828 
829   /* add &1rt {} to end of paragraph */
830   New(gp, GAP_OBJ);  hspace(gp) = 1;  vspace(gp) = 0;
831   SetGap(gap(gp), FALSE, FALSE, TRUE, AVAIL_UNIT, TAB_MODE, 1*FR);
832   tmp = MakeWord(WORD, STR_GAP_RJUSTIFY, &fpos(x));
833   Link(gp, tmp);  Link(x, gp);
834   tmp = MakeWord(WORD, STR_EMPTY, &fpos(x));
835   back(tmp, COLM) = fwd(tmp, COLM) = back(tmp, ROWM) = fwd(tmp, ROWM) = 0;
836   word_font(tmp) = 0;
837   word_colour(tmp) = 0;
838   word_underline_colour(tmp) = 0;
839   word_texture(tmp) = 1;
840   word_outline(tmp) = 0;
841   word_language(tmp) = 0;
842   word_baselinemark(tmp) = FALSE;
843   word_strut(tmp) = FALSE;
844   word_ligatures(tmp) = TRUE;
845   word_hyph(tmp) = 0;
846   underline(tmp) = UNDER_OFF;
847   Link(x, tmp);
848 
849   /* if extend_unbreakable, run through x and set every gap in the     */
850   /* shadow of a previous gap to be unbreakable                        */
851   if( extend_unbreakable )
852   { int f, max_f;  OBJECT g;
853     FirstDefinite(x, link, y, jn);
854     assert( link != x, "FillObject/extend_unbreakable:  link == x!" );
855     f = max_f = size(y, COLM);  prev = y;
856     NextDefiniteWithGap(x, link, y, g, jn);
857     while( link != x )
858     {
859       /* add unbreakableness if gap is overshadowed by a previous one */
860       f += MinGap(fwd(prev, COLM), back(y, COLM), fwd(y, COLM), &gap(g))
861 	     - fwd(prev, COLM) + back(y, COLM);
862       if( f < max_f )
863       { if( units(gap(g)) == FIXED_UNIT )
864 	  nobreak(gap(g)) = TRUE;
865       }
866       else
867       { max_f = f;
868       }
869 
870       /* on to next component and gap */
871       prev = y;
872       NextDefiniteWithGap(x, link, y, g, jn);
873     }
874   }
875 
876   /* initially we can hyphenate if hyphenation is on, but not first pass */
877   if( hyph_style(save_style(x)) == HYPH_UNDEF )
878     Error(14, 7, "hyphen or nohyphen option missing", FATAL, &fpos(x));
879   hyph_allowed = FALSE;
880 
881   /* initialize I to first interval, BestI to best ending here, and run */
882   RESTART:
883   IntervalInit(I, x, max_width, etc_width, hyph_word);  BestI = I;
884   while( IntervalClass(I) != AT_END )
885   {
886     debug0(DOF, DD, "loop:");
887     debug1(DOF, DD, "       %s", IntervalPrint(I, x));
888     switch( IntervalClass(I) )
889     {
890 
891       case TOO_LOOSE:
892       case EMPTY_INTERVAL:
893 
894 	/* too loose, so save best and shift right end */
895 	if( IntervalClass(I) == EMPTY_INTERVAL ||
896 	    IntervalBadness(BestI) <= IntervalBadness(I) )
897 	      I = BestI;
898 	debug1(DOF, DD, "BestI: %s", IntervalPrint(I, x));
899 	debug0(DOF, DD, "");
900 	/* NB no break */
901 
902 
903       case UNBREAKABLE_RIGHT:
904 
905 	IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width);
906 	BestI = I;
907 	break;
908 
909 
910       case LOOSE:
911       case TIGHT:
912       case TOO_TIGHT:
913 
914 	/* reasonable, so check best and shift left end */
915 	if( IntervalBadness(I) < IntervalBadness(BestI) )  BestI = I;
916 	/* NB no break */
917 
918 
919       case UNBREAKABLE_LEFT:
920       case TAB_OVERLAP:
921 
922 	/* too tight, or unbreakable gap at left end, so shift left end */
923 	IntervalShiftLeftEnd(I, x, max_width, etc_width);
924 	break;
925 
926 
927       /* ***
928       case EMPTY_INTERVAL:
929 
930 	PrevDefinite(x, I.llink, y);
931 	if( can_hyphenate )
932 	{ x = Hyphenate(x);
933 	  can_hyphenate = FALSE;
934 	  hyph_allowed = TRUE;
935 	  *hyph_used = TRUE;
936 	}
937 	else CorrectOversize(x, I.llink,
938 	  (I.cwid!=nilobj) ? bfc(constraint(I.cwid)) : etc_width);
939 	goto RESTART;
940       *** */
941 
942 
943       default:
944 
945 	assert(FALSE, "FillObject: IntervalClass(I)");
946 	break;
947 
948     }
949   }
950 
951   /* do end processing */
952   ifdebug(DOF, DD,
953     debug0(DOF, DD, "final result:");
954     debug1(DOF, DD, "%s", IntervalPrint(BestI, x));
955     while( BestI.llink != x )
956     { BestI.rlink = BestI.llink;
957       Child(gp, BestI.rlink);
958       BestI.llink = save_prev(gp);
959       debug1(DOF, DD, "%s", IntervalPrint(BestI, x));
960     }
961   );
962 
963   if( can_hyphenate && IntervalBadness(BestI) > HYPH_BAD )
964   {
965     /* the result is bad enough to justify the cost of a second attempt,    */
966     /* with hyphenation turned on this time                                 */
967     x = Hyphenate(x);
968     can_hyphenate = FALSE;
969     hyph_allowed = TRUE;
970     *hyph_used = TRUE;
971     goto RESTART;
972   }
973   else if( I.llink == x )
974   { /* The result has only one line.  Since the line did not fit initially, */
975     /* this must mean either that a large word was discarded or else that   */
976     /* the line was only slightly tight                                     */
977     if( multi == nilobj )
978     { res = x;
979       back(res, COLM) = 0;  fwd(res, COLM) = max_width;
980     }
981     else
982     { New(res, VCAT);
983       adjust_cat(res) = FALSE;
984       ReplaceNode(res, x);
985       Link(res, x);
986     }
987   }
988   else
989   { OBJECT lgap, llink;
990     New(res, VCAT);
991     adjust_cat(res) = FALSE;
992     back(res, COLM) = 0;  fwd(res, COLM) = max_width;
993     ReplaceNode(res, x);
994     llink = I.llink;
995 
996     /* break the lines of x */
997     while( llink != x )
998     { New(y, ACAT);
999       adjust_cat(y) = adjust_cat(x);
1000       FposCopy(fpos(y), fpos(x));
1001       StyleCopy(save_style(y), save_style(x));
1002       if( Down(res) != res &&
1003 		(display_style(save_style(y)) == DISPLAY_ADJUST ||
1004 		 display_style(save_style(y)) == DISPLAY_OUTDENT) )
1005 	 display_style(save_style(y)) = DO_ADJUST;
1006       back(y, COLM) = 0;
1007       fwd(y, COLM) = max_width;
1008 
1009       if( marginkerning(save_style(x)) )
1010       {
1011        /* Margin kerning: look at this line's first character.  */
1012        OBJECT first_on_line, parent;
1013 
1014        /* Get the first object on this line.  */
1015        parent = NextDown(llink);
1016        Child(first_on_line, parent);
1017 
1018        if( is_word( type(first_on_line) ) )
1019          KernWordLeftMargin(first_on_line, parent);
1020       }
1021 
1022       /* if outdented paragraphs, add 2.0f @Wide & to front of new line */
1023       if( display_style(save_style(x)) == DISPLAY_OUTDENT ||
1024           display_style(save_style(x)) == DISPLAY_ORAGGED )
1025       {
1026 	OBJECT t1, t2, z;
1027 	t1 = MakeWord(WORD, STR_EMPTY, &fpos(x));
1028 	back(t1, COLM) = fwd(t1, COLM) = back(t1, ROWM) = fwd(t1, ROWM) = 0;
1029 	word_font(t1) = 0;
1030 	word_colour(t1) = 0;
1031 	word_underline_colour(t1) = 0;
1032 	word_texture(t1) = 1;
1033 	word_outline(t1) = 0;
1034 	word_language(t1) = 0;
1035 	word_baselinemark(t1) = FALSE;
1036 	word_strut(t1) = FALSE;
1037 	word_ligatures(t1) = TRUE;
1038 	word_hyph(t1) = 0;
1039 	underline(t1) = UNDER_OFF;
1040 	New(t2, WIDE);
1041 	SetConstraint(constraint(t2), MAX_FULL_LENGTH, outdent_margin,
1042 	  MAX_FULL_LENGTH);
1043 	back(t2, COLM) = 0;  fwd(t2, COLM) = outdent_margin;
1044 	underline(t2) = UNDER_OFF;
1045 	Link(t2, t1);
1046 	Link(y, t2);
1047 	New(z, GAP_OBJ);
1048 	hspace(z) = vspace(z) = 0;
1049 	SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
1050 	Link(y, z);
1051       }
1052 
1053       /* move the line to below y */
1054       TransferLinks(NextDown(llink), x, y);
1055 
1056       /* add hyphen to end of previous line, if lgap is ADD_HYPH */
1057       Child(lgap, llink);
1058       if( mode(gap(lgap)) == ADD_HYPH )
1059       { OBJECT z, tmp;
1060 	FONT_NUM font;
1061         FULL_CHAR *unacc = NULL, *word_content;
1062         unsigned word_len;
1063 
1064 	/* find word hyphen attaches to, since need its underline and font */
1065 	Child(tmp, PrevDown(LastDown(x)));  /* last is lgap, so one before */
1066 	debug2(DOF, D, "tmp = %s %s", Image(type(tmp)), EchoObject(tmp));
1067 	assert(is_word(type(tmp)), "FillObject: !is_word(type(tmp))!");
1068 	word_content = string(tmp);
1069         word_len = StringLength(word_content);
1070 
1071         /* get font information */
1072         font = word_font(tmp);
1073         if (finfo[font].font_table)
1074 	{
1075 	  MAPPING m;
1076 	  m = font_mapping(finfo[font].font_table);
1077 	  unacc = MapTable[m]->map[MAP_UNACCENTED];
1078 	}
1079 
1080 	/* add zero-width gap object */
1081         New(z, GAP_OBJ);
1082 	debug0(DOF, DD, "   adding hyphen");
1083 	debug0(DOF, DD, "");
1084 	hspace(z) = vspace(z) = 0;
1085 	/* ***
1086 	vspace(z) = 0;
1087 	if (unacc)
1088           hspace(z) =
1089 	    FontKernLength(font, unacc, word_content[word_len - 1], CH_HYPHEN);
1090         else
1091           hspace(z) = 0;
1092 	*** */
1093 	underline(z) = underline(tmp);
1094 	SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, ADD_HYPH, 0);
1095 	Link(x, z);
1096 
1097 	/* add hyphen */
1098 	z = MakeWord(WORD, STR_HYPHEN, &fpos(y));
1099 	word_font(z) = word_font(tmp);
1100 	word_colour(z) = word_colour(tmp);
1101 	word_underline_colour(z) = word_underline_colour(tmp);
1102 	word_texture(z) = word_texture(tmp);
1103 	word_outline(z) = word_outline(tmp);
1104 	word_language(z) = word_language(tmp);
1105 	word_baselinemark(z) = word_baselinemark(tmp);
1106 	word_strut(z) = word_strut(tmp);
1107 	word_ligatures(z) = word_ligatures(tmp);
1108 	word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON;
1109 	underline(z) = underline(tmp);
1110 	FontWordSize(z);
1111 	if( marginkerning(save_style(x)) )
1112         {
1113           /* Margin kerning: set the hyphen's width to zero.  */
1114           back(z, COLM) = 0;
1115           fwd(z, COLM) = 0;
1116         }
1117 	Link(x, z);
1118       }
1119       else if( marginkerning(save_style(x)) )
1120       {
1121         /* Margin kerning: look at the height of this line's last char.  */
1122         OBJECT last_on_line;
1123 
1124         /* Get the last object on this line.  */
1125         Child(last_on_line, PrevDown(LastDown(x)));
1126         if( is_word(type(last_on_line)) )
1127           KernWordRightMargin(last_on_line, x);
1128       }
1129 
1130       /* attach y to res, recycle lgap for gap separating the two lines */
1131       Link(NextDown(res), y);
1132       MoveLink(llink, NextDown(res), PARENT);
1133       hspace(lgap) = 0;
1134       vspace(lgap) = 1;
1135       GapCopy(gap(lgap), line_gap(save_style(x)));
1136       if( Down(lgap) != lgap )  DisposeChild(Down(lgap));
1137 
1138       /* move on to previous line */
1139       llink = save_prev(lgap);
1140     }
1141 
1142     /* attach first line, x, to res */
1143     Link(NextDown(res), x);
1144     back(x, COLM) = 0;
1145     fwd(x, COLM) = max_width;
1146     if( display_style(save_style(x)) == DISPLAY_ADJUST ||
1147 	display_style(save_style(x)) == DISPLAY_OUTDENT )
1148 	  display_style(save_style(x)) = DO_ADJUST;
1149 
1150     /* if last line contains only the {} from final &1rt {}, delete the line */
1151     /* and the preceding gap                                                 */
1152     Child(y, LastDown(res));
1153     if( Down(y) == LastDown(y) )
1154     { DisposeChild(LastDown(res));
1155       assert( Down(res) != LastDown(res), "almost empty paragraph!" );
1156       DisposeChild(LastDown(res));
1157     }
1158 
1159     /* else delete the final &1rt {} from the last line, to help clines */
1160     else
1161     { Child(z, LastDown(y));
1162       assert( type(z)==WORD && string(z)[0]=='\0', "FillObject: last word!" );
1163       DisposeChild(LastDown(y));
1164       Child(z, LastDown(y));
1165       assert( type(z) == GAP_OBJ, "FillObject: last gap_obj!" );
1166       DisposeChild(LastDown(y));
1167     }
1168 
1169     /* set unbreakable bit of first and last inter-line gaps, if required */
1170     if( nobreakfirst(save_style(x)) && Down(res) != LastDown(res) )
1171     { Child(gp, NextDown(Down(res)));
1172       assert( type(gp) == GAP_OBJ, "FillObject: type(gp) != GAP_OBJ (a)!" );
1173       nobreak(gap(gp)) = TRUE;
1174     }
1175     if( nobreaklast(save_style(x)) && Down(res) != LastDown(res) )
1176     { Child(gp, PrevDown(LastDown(res)));
1177       assert( type(gp) == GAP_OBJ, "FillObject: type(gp) != GAP_OBJ (b)!" );
1178       nobreak(gap(gp)) = TRUE;
1179     }
1180 
1181     /* recalculate the width of the last line, since it may now be smaller */
1182     assert( LastDown(res) != res, "FillObject: empty paragraph!" );
1183     Child(y, LastDown(res));
1184     FirstDefinite(y, link, z, jn);
1185     assert( link != y, "FillObject: last line is empty!" );
1186     f = back(z, COLM);  prev = z;
1187     NextDefiniteWithGap(y, link, z, gp, jn);
1188     while( link != y )
1189     {
1190       f += MinGap(fwd(prev, COLM), back(z, COLM), fwd(z, COLM), &gap(gp));
1191       prev = z;
1192       NextDefiniteWithGap(y, link, z, gp, jn);
1193     }
1194     fwd(y, COLM) = find_min(MAX_FULL_LENGTH, f + fwd(prev, COLM));
1195 
1196     /* make last line DO_ADJUST if it is oversize */
1197     if( size(y, COLM) > max_width )  display_style(save_style(y)) = DO_ADJUST;
1198   }
1199 
1200   /* rejoin unused hyphenated gaps so that kerning will work across them */
1201   if( *hyph_used && type(res) == VCAT )
1202   { for( link = Down(res);  link != res;  link = NextDown(link) )
1203     { Child(y, link);
1204       if( type(y) == ACAT )
1205       { for( ylink = Down(y);  ylink != y;  ylink = NextDown(ylink) )
1206         { Child(gp, ylink);
1207 	  if( type(gp) == GAP_OBJ && width(gap(gp)) == 0 &&
1208 	      mode(gap(gp)) == ADD_HYPH )
1209 	  {
1210 	    /* possible candidate for joining, look into what's on each side */
1211 	    Child(prev, PrevDown(ylink));
1212 	    Child(next, NextDown(ylink));
1213 	    if( is_word(type(prev)) && is_word(type(next)) &&
1214 	        word_font(prev) == word_font(next) &&
1215 	        word_colour(prev) == word_colour(next) &&
1216 	        word_underline_colour(prev) == word_underline_colour(next) &&
1217 	        word_texture(prev) == word_texture(next) &&
1218 	        word_outline(prev) == word_outline(next) &&
1219 	        word_language(prev) == word_language(next) &&
1220 	        word_baselinemark(prev) == word_baselinemark(next) &&
1221 	        word_strut(prev) == word_strut(next) &&
1222 	        word_ligatures(prev) == word_ligatures(next) &&
1223 	        underline(prev) == underline(next) )
1224 	    {
1225 	      debug2(DOF, DD, "joining %s with %s", EchoObject(prev),
1226 		EchoObject(next));
1227 	      typ = type(prev) == QWORD || type(next) == QWORD ? QWORD : WORD;
1228 	      tmp = MakeWordTwo(typ, string(prev), string(next), &fpos(prev));
1229 	      word_font(tmp) = word_font(prev);
1230 	      word_colour(tmp) = word_colour(prev);
1231 	      word_underline_colour(tmp) = word_underline_colour(prev);
1232 	      word_texture(tmp) = word_texture(prev);
1233 	      word_outline(tmp) = word_outline(prev);
1234 	      word_language(tmp) = word_language(prev);
1235 	      word_baselinemark(tmp) = word_baselinemark(prev);
1236 	      word_strut(tmp) = word_strut(prev);
1237 	      word_ligatures(tmp) = word_ligatures(prev);
1238 	      word_hyph(tmp) = word_hyph(prev);
1239 	      FontWordSize(tmp);
1240 	      underline(tmp) = underline(prev);
1241 	      MoveLink(ylink, tmp, CHILD);
1242 	      DisposeChild(Up(prev));
1243 	      DisposeChild(Up(next));
1244 	    }
1245 	  }
1246         }
1247       }
1248     }
1249   }
1250 
1251   debug0(DOF, D, "FillObject exiting");
1252   return res;
1253 } /* end FillObject */
1254