1 /*@z13.c:Object Breaking:BreakJoinedGroup()@**********************************/
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:         z13.c                                                      */
26 /*  MODULE:       Object Breaking                                            */
27 /*  EXTERNS:      BreakObject()                                              */
28 /*                                                                           */
29 /*****************************************************************************/
30 #include "externs.h"
31 #define	broken(x)	back(x, ROWM)	/* OK since no vertical sizes yet    */
32 
33 #if DEBUG_ON
34 static int debug_depth = 1;
35 static int debug_depth_max = 5;
36 #endif
37 
38 
39 /*****************************************************************************/
40 /*                                                                           */
41 /*  static BreakJoinedGroup(start, stop, m, c, res_back, res_fwd)            */
42 /*                                                                           */
43 /*  Break joined group of components of a VCAT, beginning from Child(start)  */
44 /*  inclusive and ending at Child(stop) inclusive.  Break component m first  */
45 /*  because it is the widest.                                                */
46 /*                                                                           */
47 /*****************************************************************************/
48 
BreakJoinedGroup(OBJECT start,OBJECT stop,OBJECT m,CONSTRAINT * c,FULL_LENGTH * res_back,FULL_LENGTH * res_fwd)49 static void BreakJoinedGroup(OBJECT start, OBJECT stop, OBJECT m,
50 CONSTRAINT *c, FULL_LENGTH *res_back, FULL_LENGTH *res_fwd)
51 { OBJECT y = nilobj, link;  FULL_LENGTH b, f, sb, sf;  CONSTRAINT yc;
52   debug1(DOB, DD, "[ BreakJoinedGroup(start, stop, m, %s, -, -)",
53     EchoConstraint(c));
54 
55   /* work out a suitable constraint to apply to each component */
56   sb = sf = 0;
57   for( link = start;  link != NextDown(stop);  link = NextDown(link) )
58   { Child(y, link);
59     if( !is_definite(type(y)) )  continue;
60     sb = find_max(sb, back(y, COLM));
61     sf = find_max(sf, fwd(y, COLM));
62   }
63   if( sb <= bc(*c) )
64   {
65     /* make sure the constraint will accept objects with size (sb, 0) */
66     b = sb;
67     f = 0;
68   }
69   else
70   {
71     /* sb is too wide anyway, so don't worry about it */
72     b = 0;
73     f = 0;
74   }
75   SetConstraint(yc, find_min(bc(*c), bfc(*c)-f), bfc(*c), find_min(fc(*c), bfc(*c)-b));
76 
77   /* apply this constraint to each component in turn, m first */
78   if( m != nilobj )
79   {
80     debug1(DOB, DD, "  +++BreakJoinedGroup calling first child, yc = %s",
81       EchoConstraint(&yc));
82     m = BreakObject(m, &yc);
83     b = back(m, COLM);
84     f = fwd(m, COLM);
85     SetConstraint(yc, find_min(bc(yc), bfc(yc)-f), bfc(yc), find_min(fc(yc), bfc(yc)-b));
86   }
87   else b = f = 0;
88   for( link = start;  link != NextDown(stop);  link = NextDown(link) )
89   { Child(y, link);
90     if( !is_definite(type(y)) || y == m )  continue;
91     debug1(DOB, DD, "  +++BreakJoinedGroup calling child, yc = %s",
92       EchoConstraint(&yc));
93     y = BreakObject(y, &yc);
94     b = find_max(b, back(y, COLM));
95     f = find_max(f, fwd(y, COLM));
96     SetConstraint(yc, find_min(bc(yc), bfc(yc)-f), bfc(yc), find_min(fc(yc), bfc(yc)-b));
97   }
98   if( !FitsConstraint(b, f, *c) )
99   { debug3(DOB, DD, "  in BreakJoinedGroup: !FitsConstraint(%s, %s, %s)",
100       EchoLength(b), EchoLength(f), EchoConstraint(c));
101     Error(13, 1, "failed to break column to fit into its available space",
102       WARN, m != nilobj ? &fpos(m) : (y != nilobj ? &fpos(y) : no_fpos));
103   }
104   *res_back = b;  *res_fwd = f;
105   debug2(DOB, DD,"] BreakJoinedGroup returning (%s, %s)",
106 	EchoLength(b), EchoLength(f));
107 } /* end BreakJoinedGroup */
108 
109 
110 /*@::BreakVcat()@*************************************************************/
111 /*                                                                           */
112 /*  static OBJECT BreakVcat(x, c)                                            */
113 /*                                                                           */
114 /*  Break a VCAT to satisfy constraint c.  This is tedious because every     */
115 /*  group of components between //  ...  // must be broken separately.       */
116 /*                                                                           */
117 /*****************************************************************************/
118 
BreakVcat(OBJECT x,CONSTRAINT * c)119 static OBJECT BreakVcat(OBJECT x, CONSTRAINT *c)
120 { OBJECT y, link, start_group, m = nilobj;
121   FULL_LENGTH b, f, dble_fwd;  CONSTRAINT tc;
122   BOOLEAN dble_found;
123   debug1(DOB, DD, "[ BreakVcat(x, %s)", EchoConstraint(c));
124   assert(Down(x) != x, "BreakVcat: Down(x) == x!" );
125   SetConstraint(tc, MAX_FULL_LENGTH, find_min(bfc(*c), fc(*c)), MAX_FULL_LENGTH);
126 
127   dble_found = FALSE;  dble_fwd = 0;  start_group = nilobj;
128   for( link = Down(x);  link != x;  link = NextDown(link) )
129   { Child(y, link);
130     if( is_index(type(y)) )  continue;
131     if( type(y) == GAP_OBJ )
132     { assert( start_group != nilobj, "BreakVcat: start_group == nilobj!" );
133       if( !join(gap(y)) )
134       {
135 	/* finish off and break this group */
136 	if( !FitsConstraint(b, f, tc) )
137 	  BreakJoinedGroup(start_group, link, m, &tc, &b, &f);
138 	dble_found = TRUE;
139 	dble_fwd = find_max(dble_fwd, b + f);
140 	start_group = nilobj;
141 	debug1(DOB, DD, "  end group, dble_fwd: %s", EchoLength(dble_fwd));
142       }
143     }
144     else if( start_group == nilobj )
145     {
146       /* start new group */
147       b = back(y, COLM);  f = fwd(y, COLM);
148       start_group = link;  m = y;
149       debug2(DOB, DD, "  starting group (b = %s, f = %s):",
150 	EchoLength(b), EchoLength(f));
151       ifdebug(DOB, DD, DebugObject(y));
152     }
153     else
154     {
155       /* continue with current group */
156       b = find_max(b, back(y, COLM));  f = find_max(f, fwd(y, COLM));
157       if( fwd(y, COLM) > fwd(m, COLM) )  m = y;
158       debug3(DOB, DD, "  in group%s (b = %s, f = %s):",
159 	m == y ? " (new max)" : "",
160 	EchoLength(b), EchoLength(f));
161       ifdebug(DOB, DD, DebugObject(y));
162     }
163   }
164   assert( start_group != nilobj, "BreakVcat: start_group == nilobj (2)!" );
165 
166   if( dble_found )
167   {
168     /* finish off and break this last group, and set sizes of x */
169     if( !FitsConstraint(b, f, tc) )
170       BreakJoinedGroup(start_group, LastDown(x), m, &tc, &b, &f);
171     dble_fwd = find_max(dble_fwd, b + f);
172     debug1(DOB, DD, "  ending last group, dble_fwd: %s",EchoLength(dble_fwd));
173     back(x, COLM) = 0;  fwd(x, COLM) = find_min(MAX_FULL_LENGTH, dble_fwd);
174   }
175   else
176   {
177     /* finish off and break this last and only group, and set sizes of x */
178     debug2(DOB, DD, "  BreakVcat ending last and only group (%s, %s)",
179 	EchoLength(b), EchoLength(f));
180     BreakJoinedGroup(start_group, LastDown(x), m, c, &b, &f);
181     back(x, COLM) = b;  fwd(x, COLM) = f;
182   }
183 
184   debug0(DOB, DD, "] BreakVcat returning x:");
185   ifdebug(DOB, DD, DebugObject(x));
186   debug2(DOB, DD, "  (size is %s, %s)",
187 	EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
188   return x;
189 } /* end BreakVcat */
190 
191 
192 /*@::BreakTable()@************************************************************/
193 /*                                                                           */
194 /*  static OBJECT BreakTable(x, c)                                           */
195 /*                                                                           */
196 /*  Break table (HCAT) x to satisfy constraint c.                            */
197 /*                                                                           */
198 /*  Outline of algorithm:                                                    */
199 /*                                                                           */
200 /*     bcount = number of components to left of mark;                        */
201 /*     fcount = no. of components on and right of mark;                      */
202 /*     bwidth = what back(x) would be if all components had size (0, 0);     */
203 /*     fwidth = what fwd(x) would be if all components had size (0, 0);      */
204 /*     Set all components of x to Unbroken (broken(y) holds this flag);      */
205 /*     while( an Unbroken component of x exists )                            */
206 /*     {   my = the Unbroken component of x of minimum width;                */
207 /*         mc = desirable constraint for my (see below);                     */
208 /*         BreakObject(my, &mc);                                             */
209 /*         Set my to Broken and update bcount, fcount, bwidth, fwidth        */
210 /*            to reflect the actual size of my, now broken;                  */
211 /*     }                                                                     */
212 /*                                                                           */
213 /*  The constraint mc is chosen in an attempt to ensure that:                */
214 /*                                                                           */
215 /*     a)  Any sufficiently narrow components will not break;                */
216 /*     b)  All broken components will have the same bfc(mc), if possible;    */
217 /*     c)  All available space is used.                                      */
218 /*                                                                           */
219 /*****************************************************************************/
220 
BreakTable(OBJECT x,CONSTRAINT * c)221 static OBJECT BreakTable(OBJECT x, CONSTRAINT *c)
222 { FULL_LENGTH bwidth, fwidth;	/* running back(x) and fwd(x)		     */
223   int    bcount, fcount;	/* running no. of components		     */
224   OBJECT mlink = nilobj, my;	/* minimum-width unbroken component	     */
225   BOOLEAN ratm = FALSE;		/* TRUE when my has a mark to its right      */
226   int    mside;			/* side of the mark my is on: BACK, ON, FWD  */
227   FULL_LENGTH msize;		/* size of my (minimal among unbroken)	     */
228   CONSTRAINT mc;		/* desirable constraint for my		     */
229   OBJECT pg, prec_def;		/* preceding definite object of my           */
230   OBJECT sg, succ_def;		/* succeeding definite object of my          */
231   FULL_LENGTH pd_extra,sd_extra;/* space availiable for free each side of my */
232   FULL_LENGTH av_colsize;	/* the size of each unbroken component       */
233 				/* if they are all assigned equal width      */
234   FULL_LENGTH fwd_max, back_max;/* maximum space available forward of or     */
235 				/* back of the mark, when columns are even   */
236   FULL_LENGTH col_size = 0;	/* the column size actually used in breaking */
237   FULL_LENGTH prev_col_size;	/* previous column size (try to keep equal)  */
238   FULL_LENGTH beffect, feffect;	/* the amount bwidth, fwidth must increase   */
239 				/* when my is broken			     */
240   OBJECT link, y, prev, g;  FULL_LENGTH tmp, tmp2;
241 
242   debug1(DOB, DD, "[ BreakTable( x, %s )", EchoConstraint(c));
243 
244   /* Initialise csize, bcount, fcount, bwidth, fwidth and broken(y) */
245   bcount = fcount = 0;  bwidth = fwidth = 0;  prev = nilobj;
246   prev_col_size = 0;
247   Child(y, Down(x));
248   assert( type(y) != GAP_OBJ, "BreakTable: GAP_OBJ!" );
249   assert( !is_index(type(y)), "BreakTable: index!" );
250   broken(y) = is_indefinite(type(y));
251   if( !broken(y) )  prev = y, fcount = 1;
252 
253   for( link = NextDown(Down(x));  link != x;  link = NextDown(NextDown(link)) )
254   {
255     /* find the next gap g and following child y */
256     Child(g, link);
257     assert( type(g) == GAP_OBJ, "BreakTable: GAP_OBJ!" );
258     assert( NextDown(link) != x, "BreakTable: GAP_OBJ is last!" );
259     Child(y, NextDown(link));
260 
261     assert( type(y) != GAP_OBJ, "BreakTable: GAP_OBJ!" );
262     assert( !is_index(type(y)), "BreakTable: index!" );
263     broken(y) = is_indefinite(type(y));
264     if( !broken(y) )
265     { if( prev == nilobj )  fcount = 1;
266       else if( mark(gap(g)) )
267       {	bcount += fcount;
268 	bwidth += fwidth + MinGap(0, 0, 0, &gap(g));
269 	fcount  = 1;  fwidth = 0;
270       }
271       else
272       {	fwidth += MinGap(0, 0, 0, &gap(g));
273 	fcount += 1;
274       }
275       prev = y;
276     }
277   }
278 
279   /* if column gaps alone are too wide, kill them all */
280   if( !FitsConstraint(bwidth, fwidth, *c) )
281   {
282     debug2(DOB, DD, "column gaps alone too wide: bwidth: %s; fwidth: %s",
283        EchoLength(bwidth), EchoLength(fwidth));
284     Error(13, 2, "reducing column gaps to 0i (object is too wide)",
285       WARN, &fpos(x));
286     for( link = Down(x);  link != x;  link = NextDown(link) )
287     { Child(g, link);
288       if( type(g) == GAP_OBJ )
289       {	SetGap(gap(g), nobreak(gap(g)), mark(gap(g)), join(gap(g)),
290 	  FIXED_UNIT, EDGE_MODE, 0);
291       }
292     }
293     bwidth = fwidth = 0;
294   }
295 
296   /* break each column, from smallest to largest */
297   while( bcount + fcount > 0 && FitsConstraint(bwidth, fwidth, *c) )
298   {
299     debug2(DOB, DD, "bcount: %d;  bwidth: %s", bcount, EchoLength(bwidth));
300     debug2(DOB, DD, "fcount: %d;  fwidth: %s", fcount, EchoLength(fwidth));
301 
302     /* find a minimal-width unbroken component my */
303     my = nilobj;  msize = size(x, COLM);       /* an upper bound for size(y) */
304     for( link = Down(x);  ;  link = NextDown(link) )
305     { Child(y, link);
306       assert( type(y) != GAP_OBJ, "BreakTable: type(y) == GAP_OBJ!" );
307       if( !broken(y) && (size(y, COLM) < msize || my == nilobj) )
308       {	msize = size(y, COLM);
309 	my = y;  mlink = link;
310 	ratm = FALSE;
311       }
312 
313       /* next gap */
314       link = NextDown(link);
315       if( link == x )  break;
316       Child(g, link);
317       assert( type(g) == GAP_OBJ, "BreakTable: type(g) != GAP_OBJ!" );
318       if( mark(gap(g)) )  ratm = TRUE;
319     }
320 
321     /* find neighbouring definite objects and resulting pd_extra and sd_extra */
322     SetNeighbours(mlink, ratm, &pg, &prec_def, &sg, &succ_def, &mside);
323     debug2(DOB, DD, "my (%s): %s", Image(mside), EchoObject(my));
324     pd_extra = pg == nilobj ? 0 :
325       ExtraGap(broken(prec_def) ? fwd(prec_def,COLM) : 0, 0, &gap(pg), BACK);
326     sd_extra = sg == nilobj ? 0 :
327       ExtraGap(0, broken(succ_def) ? back(succ_def,COLM) : 0, &gap(sg), FWD);
328     debug2(DOB, DD, "pd_extra:   %s;  sd_extra:      %s",
329 		EchoLength(pd_extra), EchoLength(sd_extra) );
330 
331     /* calculate desirable constraints for my */
332     av_colsize = (bfc(*c) - bwidth - fwidth) / (bcount + fcount);
333     debug1(DOB, DD, "av_colsize = %s", EchoLength(av_colsize));
334     debug1(DOB, DD, "prev_col_size = %s", EchoLength(prev_col_size));
335     switch( mside )
336     {
337 
338       case BACK:
339 
340 	back_max = find_min(bc(*c), bwidth + av_colsize * bcount);
341 	col_size = (back_max - bwidth) / bcount;
342 	if( col_size > prev_col_size && col_size - prev_col_size < PT )
343 	  col_size = prev_col_size;
344 	SetConstraint(mc,
345 	  find_min(MAX_FULL_LENGTH, col_size + pd_extra),
346 	  find_min(MAX_FULL_LENGTH, col_size + pd_extra + sd_extra),
347 	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
348 	break;
349 
350 
351       case ON:
352 
353 	fwd_max = find_min(fc(*c), fwidth + av_colsize * fcount);
354 	col_size = (fwd_max - fwidth) / fcount;
355 	if( col_size > prev_col_size && col_size - prev_col_size < PT )
356 	  col_size = prev_col_size;
357 	SetConstraint(mc,
358 	  find_min(MAX_FULL_LENGTH, pd_extra + back(my, COLM)),
359 	  find_min(MAX_FULL_LENGTH, pd_extra + back(my, COLM) + col_size + sd_extra),
360 	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
361 	break;
362 
363 
364       case FWD:
365 
366 	fwd_max = find_min(fc(*c), fwidth + av_colsize * fcount);
367 	col_size = (fwd_max - fwidth) / fcount;
368 	if( col_size > prev_col_size && col_size - prev_col_size < PT )
369 	  col_size = prev_col_size;
370 	SetConstraint(mc,
371 	  find_min(MAX_FULL_LENGTH, col_size + pd_extra),
372 	  find_min(MAX_FULL_LENGTH, col_size + pd_extra + sd_extra),
373 	  find_min(MAX_FULL_LENGTH, col_size + sd_extra));
374 	break;
375 
376 
377       default:
378 
379 	assert(FALSE, "BreakTable: mside");
380 	break;
381     }
382     debug1(DOB, DD, "col_size = %s", EchoLength(col_size));
383     prev_col_size = col_size;
384 
385     /* now break my according to these constraints, and accept it */
386     debug2(DOB, DD, "  calling BreakObject(%s, %s)", EchoObject(my),
387       EchoConstraint(&mc));
388     my = BreakObject(my, &mc);  broken(my) = TRUE;
389 
390     /* calculate the effect of accepting my on bwidth and fwidth */
391     if( pg != nilobj )
392     { tmp = broken(prec_def) ? fwd(prec_def, COLM) : 0;
393       beffect = MinGap(tmp, back(my, COLM), fwd(my, COLM), &gap(pg)) -
394 	        MinGap(tmp, 0,             0,            &gap(pg));
395     }
396     else beffect = back(my, COLM);
397 
398     if( sg != nilobj )
399     { tmp = broken(succ_def) ? back(succ_def, COLM) : 0;
400       tmp2 = broken(succ_def) ? fwd(succ_def, COLM) : 0;
401       feffect = MinGap(fwd(my, COLM), tmp, tmp2, &gap(sg)) -
402 	        MinGap(0,            tmp, tmp2, &gap(sg));
403     }
404     else feffect = fwd(my, COLM);
405 
406     switch( mside )
407     {
408 	case BACK:	bwidth += beffect + feffect;
409 			bcount--;
410 			break;
411 
412 	case ON:	bwidth += beffect;  fwidth += feffect;
413 			fcount--;
414 			break;
415 
416 	case FWD:	fwidth += beffect + feffect;
417 			fcount--;
418 			break;
419 
420 	default:	assert(FALSE, "BreakTable: mside");
421 			break;
422     }
423 
424   } /* end while */
425 
426   back(x, COLM) = bwidth;
427   fwd(x, COLM) = fwidth;
428 
429   debug2(DOB, DD,  "] BreakTable returning %s,%s; x =",
430     EchoLength(bwidth), EchoLength(fwidth));
431   ifdebug(DOB, DD, DebugObject(x));
432   return x;
433 } /* end BreakTable */
434 
435 
436 /*@::BreakObject()@***********************************************************/
437 /*                                                                           */
438 /*  OBJECT BreakObject(x, c)                                                 */
439 /*                                                                           */
440 /*  Break lines of object x so that it satisfies constraint c.               */
441 /*                                                                           */
442 /*****************************************************************************/
443 
BreakObject(OBJECT x,CONSTRAINT * c)444 OBJECT BreakObject(OBJECT x, CONSTRAINT *c)
445 { OBJECT link, y;  CONSTRAINT yc;  FULL_LENGTH f;  BOOLEAN junk;
446   debugcond4(DOB, D, debug_depth++ < debug_depth_max,
447     "%*s[ BreakObject(%s %d)", (debug_depth-1)*2, " ", Image(type(x)), (int) x);
448   debug4(DOB, DD,  "[ BreakObject(%s (%s,%s),  %s), x =",
449     Image(type(x)), EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)),
450     EchoConstraint(c));
451   ifdebug(DOB, DD, DebugObject(x));
452 
453   /* if constraint is negative (should really be never), replace with empty */
454   if( !(bc(*c)>=0 && bfc(*c)>=0 && fc(*c)>=0) )
455   {
456     Error(13, 11, "replacing with empty object: negative size constraint %s,%s,%s",
457       WARN, &fpos(x), EchoLength(bc(*c)), EchoLength(bfc(*c)), EchoLength(fc(*c)));
458     y = MakeWord(WORD, STR_EMPTY, &fpos(x));
459     back(y, COLM) = fwd(y, COLM) = 0;
460     ReplaceNode(y, x);
461     DisposeObject(x);
462     x = y;
463     debugcond6(DOB, D, --debug_depth < debug_depth_max,
464       "%*s] BreakObject(%s %d) (neg!) = (%s, %s)", debug_depth*2, " ",
465       Image(type(x)), (int) x, EchoLength(back(x, COLM)),
466       EchoLength(fwd(x, COLM)));
467     debug0(DOB, DD, "] BreakObject returning (negative constraint).");
468     return x;
469   }
470 
471   /* if no breaking required, return immediately */
472   if( FitsConstraint(back(x, COLM), fwd(x, COLM), *c) )
473   { debug0(DOB, DD, "] BreakObject returning (fits).");
474     debugcond6(DOB, D, --debug_depth < debug_depth_max,
475       "%*s] BreakObject(%s %d) (fits) = (%s, %s)", debug_depth*2, " ",
476       Image(type(x)), (int) x, EchoLength(back(x, COLM)),
477       EchoLength(fwd(x, COLM)));
478     return x;
479   }
480 
481   switch( type(x) )
482   {
483 
484     case ROTATE:
485 
486       if( BackEnd->scale_avail && InsertScale(x, c) )
487       {
488 	Parent(x, Up(x));
489 	Error(13, 3, "%s object scaled horizontally by factor %.2f (too wide)",
490 	  WARN, &fpos(x), KW_ROTATE, (float) bc(constraint(x)) / SF );
491       }
492       else
493       { Error(13, 4, "%s deleted (too wide; cannot break %s)",
494 	  WARN, &fpos(x), KW_ROTATE, KW_ROTATE);
495         y = MakeWord(WORD, STR_EMPTY, &fpos(x));
496         back(y, COLM) = fwd(y, COLM) = 0;
497         ReplaceNode(y, x);
498         DisposeObject(x);
499         x = y;
500       }
501       break;
502 
503 
504     case SCALE:
505 
506       InvScaleConstraint(&yc, bc(constraint(x)), c);
507       Child(y, Down(x));
508       y = BreakObject(y, &yc);
509       back(x, COLM) = (back(y, COLM) * bc(constraint(x))) / SF;
510       fwd(x, COLM) =  (fwd(y, COLM)  * bc(constraint(x))) / SF;
511       break;
512 
513 
514     case KERN_SHRINK:
515 
516       /* not really accurate, but there you go */
517       Child(y, LastDown(x));
518       y = BreakObject(y, c);
519       back(x, COLM) = back(y, COLM);
520       fwd(x, COLM) = fwd(y, COLM);
521       break;
522 
523 
524     case WORD:
525     case QWORD:
526 
527       if( word_hyph(x) )
528       {
529 	/* create an ACAT with the same size as x */
530 	New(y, ACAT);
531 	FposCopy(fpos(y), fpos(x));
532 	back(y, COLM) = back(x, COLM);
533 	fwd(y, COLM) = fwd(x, COLM);
534 	back(y, ROWM) = back(x, ROWM);
535 	fwd(y, ROWM) = fwd(x, ROWM);
536 
537 	/* set ACAT's save_style; have to invent a line_gap, unfortunately */
538 	SetGap(line_gap(save_style(y)), FALSE, FALSE, FALSE, FIXED_UNIT,
539 	  MARK_MODE, 1.1 * FontSize(word_font(x), x));
540 	SetGap(space_gap(save_style(y)), FALSE, FALSE, TRUE, FIXED_UNIT,
541 	  EDGE_MODE, 0);
542 	hyph_style(save_style(y)) = HYPH_ON;
543 	fill_style(save_style(y)) = FILL_ON;
544 	display_style(save_style(y)) = DISPLAY_LEFT;
545 	small_caps(save_style(y)) = FALSE;
546 	font(save_style(y)) = word_font(x);
547 	colour(save_style(y)) = word_colour(x);
548 	underline_colour(save_style(y)) = word_underline_colour(x);
549 	texture(save_style(y)) = word_texture(x);
550 	outline(save_style(y)) = word_outline(x);
551 	language(save_style(y)) = word_language(x);
552 	baselinemark(save_style(y)) = word_baselinemark(x);
553 	strut(save_style(y)) = word_strut(x);
554 	ligatures(save_style(y)) = word_ligatures(x);
555 	debug3(DOF, DD, "  in BreakObject y %s %s %s",
556 	  EchoStyle(&save_style(y)), Image(type(y)), EchoObject(y));
557 
558 	/* enclose x in the ACAT and try breaking (i.e. filling) it */
559 	ReplaceNode(y, x);
560 	Link(y, x);
561 	x = y;
562 	debug3(DOF, DD, "  in BreakObject x %s %s %s",
563 	  EchoStyle(&save_style(x)), Image(type(x)), EchoObject(x));
564 	x = BreakObject(x, c);
565       }
566       else if( BackEnd->scale_avail && InsertScale(x, c) )
567       { OBJECT tmp;
568 	tmp = x;
569 	Parent(x, Up(x));
570 	Error(13, 5, "word %s scaled horizontally by factor %.2f (too wide)",
571 	  WARN, &fpos(x), string(tmp), (float) bc(constraint(x)) / SF);
572       }
573       else
574       { Error(13, 6, "word %s deleted (too wide)", WARN, &fpos(x), string(x));
575         y = MakeWord(WORD, STR_EMPTY, &fpos(x));
576         back(y, COLM) = fwd(y, COLM) = 0;
577         ReplaceNode(y, x);
578         DisposeObject(x);
579         x = y;
580       }
581       break;
582 
583 
584     case WIDE:
585 
586       MinConstraint(&constraint(x), c);
587       Child(y, Down(x));
588       y = BreakObject(y, &constraint(x));
589       back(x, COLM) = back(y, COLM);
590       fwd(x, COLM) = fwd(y, COLM);
591       EnlargeToConstraint(&back(x, COLM), &fwd(x, COLM), &constraint(x));
592       break;
593 
594 
595     case INCGRAPHIC:
596     case SINCGRAPHIC:
597 
598       if( BackEnd->scale_avail && InsertScale(x, c) )
599       {
600 	Parent(x, Up(x));
601 	Error(13, 7, "%s scaled horizontally by factor %.2f (too wide)",
602 	  WARN, &fpos(x),
603 	  type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC,
604 	  (float) bc(constraint(x)) / SF);
605       }
606       else
607       { Error(13, 8, "%s deleted (too wide)", WARN, &fpos(x),
608 	  type(x) == INCGRAPHIC ? KW_INCGRAPHIC : KW_SINCGRAPHIC);
609         y = MakeWord(WORD, STR_EMPTY, &fpos(x));
610         back(y, COLM) = fwd(y, COLM) = 0;
611         ReplaceNode(y, x);
612         DisposeObject(x);
613         x = y;
614       }
615       break;
616 
617 
618     case HMIRROR:
619 
620       FlipConstraint(yc, *c);
621       Child(y, Down(x));
622       y = BreakObject(y, &yc);
623       back(x, COLM) = fwd(y, COLM);
624       fwd(x, COLM) = back(y, COLM);
625       break;
626 
627 
628     case HIGH:
629     case VMIRROR:
630     case VSCALE:
631     case VCOVER:
632     case VSHIFT:
633     case HCONTRACT:
634     case VCONTRACT:
635     case HLIMITED:
636     case VLIMITED:
637     case HEXPAND:
638     case VEXPAND:
639     case ONE_COL:
640     case ONE_ROW:
641     case HSPANNER:
642 
643       assert( Down(x) == LastDown(x), "BreakObject: downs!" );
644       Child(y, Down(x));
645       y = BreakObject(y, c);
646       back(x, COLM) = back(y, COLM);
647       fwd(x, COLM) = fwd(y, COLM);
648       break;
649 
650 
651     case BACKGROUND:
652 
653       Child(y, Down(x));
654       y = BreakObject(y, c);
655       Child(y, LastDown(x));
656       y = BreakObject(y, c);
657       back(x, COLM) = back(y, COLM);
658       fwd(x, COLM) = fwd(y, COLM);
659       break;
660 
661 
662     case START_HVSPAN:
663     case START_HSPAN:
664     case START_VSPAN:
665     case HSPAN:
666     case VSPAN:
667 
668       /* these all have size zero except the last one, so if we get to  */
669       /* this point we must be at the last column and need to break it. */
670       /* this is done just by setting its size to zero, unless it is    */
671       /* the last column in which case it claims everything that is     */
672       /* going; the real break is deferred to the first ROWM touch,     */
673       /* when we know that all contributing columns have been broken    */
674       /* unless the child is not a spanner, in which case it's @OneCol  */
675       Child(y, Down(x));
676       if( type(y) != HSPANNER )
677       {
678         y = BreakObject(y, c);
679         back(x, COLM) = back(y, COLM);
680         fwd(x, COLM) = fwd(y, COLM);
681       }
682       else
683       {
684         back(x, COLM) = 0;
685         fwd(x, COLM) = find_min(bfc(*c), fc(*c));
686       }
687       break;
688 
689 
690     case HSHIFT:
691 
692       Child(y, Down(x));
693       f = FindShift(x, y, COLM);
694       SetConstraint(yc,
695 	find_min(bc(*c), bfc(*c)) - f, bfc(*c), find_min(fc(*c), bfc(*c)) + f);
696       BreakObject(y, &yc);
697       f = FindShift(x, y, COLM);
698       back(x, COLM) = find_min(MAX_FULL_LENGTH, find_max(0, back(y, COLM) + f));
699       fwd(x, COLM)  = find_min(MAX_FULL_LENGTH, find_max(0, fwd(y, COLM)  - f));
700       break;
701 
702 
703     case END_HEADER:
704     case CLEAR_HEADER:
705 
706       /* these have size zero anyway, so not likely to reach this point */
707       break;
708 
709 
710     case BEGIN_HEADER:
711     case SET_HEADER:
712 
713       /* multiple copies, remember */
714       for( link = NextDown(Down(x));  link != x;  link = NextDown(link) )
715       {
716         Child(y, link);
717         y = BreakObject(y, c);
718         back(x, COLM) = back(y, COLM);
719         fwd(x, COLM) = fwd(y, COLM);
720       }
721       debug3(DOB, D, "BreakObject(%s, COLM) = (%s, %s)", Image(type(x)),
722 	EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
723       break;
724 
725 
726     case PLAIN_GRAPHIC:
727     case GRAPHIC:
728     case LINK_SOURCE:
729     case LINK_DEST:
730     case LINK_DEST_NULL:
731     case LINK_URL:
732 
733       Child(y, LastDown(x));
734       y = BreakObject(y, c);
735       back(x, COLM) = back(y, COLM);
736       fwd(x, COLM) = fwd(y, COLM);
737       break;
738 
739 
740     case SPLIT:
741 
742       Child(y, DownDim(x, COLM));
743       y = BreakObject(y, c);
744       back(x, COLM) = back(y, COLM);
745       fwd(x, COLM) = fwd(y, COLM);
746       break;
747 
748 
749     case ACAT:
750 
751       if( back(x, COLM) > 0 )
752       { int sz;  OBJECT rpos;
753 	/* shift the column mark of x to the left edge */
754 	sz = size(x, COLM);
755 	fwd(x, COLM) = find_min(MAX_FULL_LENGTH, sz);
756 	back(x, COLM) = 0;
757 	rpos = x;
758 	for( link = Down(x);  link != x;  link = NextDown(link) )
759 	{ Child(y, link);
760 	  if( type(y) == GAP_OBJ && mark(gap(y)) )
761 	  { mark(gap(y)) = FALSE;
762 	    rpos = y;
763 	  }
764 	}
765 	if( FitsConstraint(back(x, COLM), fwd(x, COLM), *c) )
766 	{ Error(13, 9, "column mark of unbroken paragraph moved left",
767 	    WARN, &fpos(rpos));
768 	  break;
769 	}
770 	Error(13, 10, "column mark of paragraph moved left before breaking",
771 	  WARN, &fpos(rpos));
772 	ifdebug(DOB, DD, DebugObject(x));
773       }
774       x = FillObject(x, c, nilobj, TRUE, TRUE, FALSE, &junk);
775       break;
776 
777 
778     case HCAT:
779 
780       x = BreakTable(x, c);
781       break;
782 
783 
784     case COL_THR:
785 
786       BreakJoinedGroup(Down(x), LastDown(x), nilobj, c,
787 	&back(x,COLM), &fwd(x,COLM));
788       break;
789 
790 
791     case VCAT:
792 
793       x = BreakVcat(x, c);
794       break;
795 
796 
797     default:
798 
799       assert1(FALSE, "BreakObject:", Image(type(x)));
800       break;
801 
802   }
803   assert( back(x, COLM) >= 0, "BreakObject: back(x, COLM) < 0!" );
804   assert( fwd(x, COLM) >= 0, "BreakObject: fwd(x, COLM) < 0!" );
805   debugcond6(DOB, D, --debug_depth < debug_depth_max,
806     "%*s] BreakObject(%s %d) = (%s, %s)", debug_depth*2, " ", Image(type(x)),
807     (int) x, EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
808   debug2(DOB, DD,  "] BreakObject returning %s,%s, x =",
809     EchoLength(back(x, COLM)), EchoLength(fwd(x, COLM)));
810   ifdebug(DOB, DD,  DebugObject(x));
811   return x;
812 } /* end BreakObject */
813