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