1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: SpaceResolver.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
19 
20 package org.apache.fop.layoutmgr;
21 
22 import java.util.List;
23 import java.util.ListIterator;
24 
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 
28 import org.apache.fop.traits.MinOptMax;
29 
30 /**
31  * This class resolves spaces and conditional borders and paddings by replacing the
32  * UnresolvedListElements descendants by the right combination of KnuthElements on an element
33  * list.
34  */
35 public final class SpaceResolver {
36 
37     /** Logger instance */
38     private static final Log LOG = LogFactory.getLog(SpaceResolver.class);
39 
40     private UnresolvedListElementWithLength[] firstPart;
41     private BreakElement breakPoss;
42     private UnresolvedListElementWithLength[] secondPart;
43     private UnresolvedListElementWithLength[] noBreak;
44 
45     private MinOptMax[] firstPartLengths;
46     private MinOptMax[] secondPartLengths;
47     private MinOptMax[] noBreakLengths;
48 
49     private boolean isFirst;
50     private boolean isLast;
51 
52     /**
53      * Main constructor.
54      * @param first Element list before a break (optional)
55      * @param breakPoss Break possibility (optional)
56      * @param second Element list after a break (or if no break possibility in vicinity)
57      * @param isFirst Resolution at the beginning of a (full) element list
58      * @param isLast Resolution at the end of a (full) element list
59      */
SpaceResolver(List first, BreakElement breakPoss, List second, boolean isFirst, boolean isLast)60     private SpaceResolver(List first, BreakElement breakPoss, List second,
61             boolean isFirst, boolean isLast) {
62         this.isFirst = isFirst;
63         this.isLast = isLast;
64         //Create combined no-break list
65         int c = 0;
66         if (first != null) {
67             c += first.size();
68         }
69         if (second != null) {
70             c += second.size();
71         }
72         noBreak = new UnresolvedListElementWithLength[c];
73         noBreakLengths = new MinOptMax[c];
74         int i = 0;
75         ListIterator iter;
76         if (first != null) {
77             iter = first.listIterator();
78             while (iter.hasNext()) {
79                 noBreak[i] = (UnresolvedListElementWithLength)iter.next();
80                 noBreakLengths[i] = noBreak[i].getLength();
81                 i++;
82             }
83         }
84         if (second != null) {
85             iter = second.listIterator();
86             while (iter.hasNext()) {
87                 noBreak[i] = (UnresolvedListElementWithLength)iter.next();
88                 noBreakLengths[i] = noBreak[i].getLength();
89                 i++;
90             }
91         }
92 
93         //Add pending elements from higher level FOs
94         if (breakPoss != null) {
95             if (breakPoss.getPendingAfterMarks() != null) {
96                 if (LOG.isTraceEnabled()) {
97                     LOG.trace("    adding pending before break: "
98                             + breakPoss.getPendingAfterMarks());
99                 }
100                 first.addAll(0, breakPoss.getPendingAfterMarks());
101             }
102             if (breakPoss.getPendingBeforeMarks() != null) {
103                 if (LOG.isTraceEnabled()) {
104                     LOG.trace("    adding pending after break: "
105                             + breakPoss.getPendingBeforeMarks());
106                 }
107                 second.addAll(0, breakPoss.getPendingBeforeMarks());
108             }
109         }
110         if (LOG.isTraceEnabled()) {
111             LOG.trace("before: " + first);
112             LOG.trace("  break: " + breakPoss);
113             LOG.trace("after: " + second);
114             LOG.trace("NO-BREAK: " + toString(noBreak, noBreakLengths));
115         }
116 
117         if (first != null) {
118             firstPart = new UnresolvedListElementWithLength[first.size()];
119             firstPartLengths = new MinOptMax[firstPart.length];
120             first.toArray(firstPart);
121             for (i = 0; i < firstPart.length; i++) {
122                 firstPartLengths[i] = firstPart[i].getLength();
123             }
124         }
125         this.breakPoss = breakPoss;
126         if (second != null) {
127             secondPart = new UnresolvedListElementWithLength[second.size()];
128             secondPartLengths = new MinOptMax[secondPart.length];
129             second.toArray(secondPart);
130             for (i = 0; i < secondPart.length; i++) {
131                 secondPartLengths[i] = secondPart[i].getLength();
132             }
133         }
134         resolve();
135     }
136 
toString(Object[] arr1, Object[] arr2)137     private String toString(Object[] arr1, Object[] arr2) {
138         if (arr1.length != arr2.length) {
139             throw new IllegalArgumentException("The length of both arrays must be equal");
140         }
141         StringBuffer sb = new StringBuffer("[");
142         for (int i = 0; i < arr1.length; i++) {
143             if (i > 0) {
144                 sb.append(", ");
145             }
146             sb.append(String.valueOf(arr1[i]));
147             sb.append("/");
148             sb.append(String.valueOf(arr2[i]));
149         }
150         sb.append("]");
151         return sb.toString();
152     }
153 
removeConditionalBorderAndPadding( UnresolvedListElement[] elems, MinOptMax[] lengths, boolean reverse)154     private void removeConditionalBorderAndPadding(
155             UnresolvedListElement[] elems, MinOptMax[] lengths, boolean reverse) {
156         for (int i = 0; i < elems.length; i++) {
157             int effIndex;
158             if (reverse) {
159                 effIndex = elems.length - 1 - i;
160             } else {
161                 effIndex = i;
162             }
163             if (elems[effIndex] instanceof BorderOrPaddingElement) {
164                 BorderOrPaddingElement bop = (BorderOrPaddingElement)elems[effIndex];
165                 if (bop.isConditional() && !(bop.isFirst() || bop.isLast())) {
166                     if (LOG.isDebugEnabled()) {
167                         LOG.debug("Nulling conditional element: " + bop);
168                     }
169                     lengths[effIndex] = null;
170                 }
171             }
172         }
173         if (LOG.isTraceEnabled() && elems.length > 0) {
174             LOG.trace("-->Resulting list: " + toString(elems, lengths));
175         }
176     }
177 
performSpaceResolutionRule1(UnresolvedListElement[] elems, MinOptMax[] lengths, boolean reverse)178     private void performSpaceResolutionRule1(UnresolvedListElement[] elems, MinOptMax[] lengths,
179             boolean reverse) {
180         for (int i = 0; i < elems.length; i++) {
181             int effIndex;
182             if (reverse) {
183                 effIndex = elems.length - 1 - i;
184             } else {
185                 effIndex = i;
186             }
187             if (lengths[effIndex] == null) {
188                 //Zeroed border or padding doesn't create a fence
189                 continue;
190             } else if (elems[effIndex] instanceof BorderOrPaddingElement) {
191                 //Border or padding form fences!
192                 break;
193             } else if (!elems[effIndex].isConditional()) {
194                 break;
195             }
196             if (LOG.isDebugEnabled()) {
197                 LOG.debug("Nulling conditional element using 4.3.1, rule 1: " + elems[effIndex]);
198             }
199             lengths[effIndex] = null;
200         }
201         if (LOG.isTraceEnabled() && elems.length > 0) {
202             LOG.trace("-->Resulting list: " + toString(elems, lengths));
203         }
204     }
205 
performSpaceResolutionRules2to3(UnresolvedListElement[] elems, MinOptMax[] lengths, int start, int end)206     private void performSpaceResolutionRules2to3(UnresolvedListElement[] elems,
207             MinOptMax[] lengths, int start, int end) {
208         if (LOG.isTraceEnabled()) {
209             LOG.trace("rule 2-3: " + start + "-" + end);
210         }
211         SpaceElement space;
212         int remaining;
213 
214         //Rule 2 (4.3.1, XSL 1.0)
215         boolean hasForcing = false;
216         remaining = 0;
217         for (int i = start; i <= end; i++) {
218             if (lengths[i] == null) {
219                 continue;
220             }
221             remaining++;
222             space = (SpaceElement)elems[i];
223             if (space.isForcing()) {
224                 hasForcing = true;
225                 break;
226             }
227         }
228         if (remaining == 0) {
229             return; //shortcut
230         }
231         if (hasForcing) {
232             for (int i = start; i <= end; i++) {
233                 if (lengths[i] == null) {
234                     continue;
235                 }
236                 space = (SpaceElement)elems[i];
237                 if (!space.isForcing()) {
238                     if (LOG.isDebugEnabled()) {
239                         LOG.debug("Nulling non-forcing space-specifier using 4.3.1, rule 2: "
240                                 + elems[i]);
241                     }
242                     lengths[i] = null;
243                 }
244             }
245             return; //If rule is triggered skip rule 3
246         }
247 
248         //Rule 3 (4.3.1, XSL 1.0)
249         //Determine highes precedence
250         int highestPrecedence = Integer.MIN_VALUE;
251         for (int i = start; i <= end; i++) {
252             if (lengths[i] == null) {
253                 continue;
254             }
255             space = (SpaceElement)elems[i];
256             highestPrecedence = Math.max(highestPrecedence, space.getPrecedence());
257         }
258         if (highestPrecedence != 0 && LOG.isDebugEnabled()) {
259             LOG.debug("Highest precedence is " + highestPrecedence);
260         }
261         //Suppress space-specifiers with lower precedence
262         remaining = 0;
263         int greatestOptimum = Integer.MIN_VALUE;
264         for (int i = start; i <= end; i++) {
265             if (lengths[i] == null) {
266                 continue;
267             }
268             space = (SpaceElement)elems[i];
269             if (space.getPrecedence() != highestPrecedence) {
270                 if (LOG.isDebugEnabled()) {
271                     LOG.debug("Nulling space-specifier with precedence "
272                             + space.getPrecedence() + " using 4.3.1, rule 3: "
273                             + elems[i]);
274                 }
275                 lengths[i] = null;
276             } else {
277                 greatestOptimum = Math.max(greatestOptimum, space.getLength().getOpt());
278                 remaining++;
279             }
280         }
281         if (LOG.isDebugEnabled()) {
282             LOG.debug("Greatest optimum: " + greatestOptimum);
283         }
284         if (remaining <= 1) {
285             return;
286         }
287         //Suppress space-specifiers with smaller optimum length
288         remaining = 0;
289         for (int i = start; i <= end; i++) {
290             if (lengths[i] == null) {
291                 continue;
292             }
293             space = (SpaceElement)elems[i];
294             if (space.getLength().getOpt() < greatestOptimum) {
295                 if (LOG.isDebugEnabled()) {
296                     LOG.debug("Nulling space-specifier with smaller optimum length "
297                             + "using 4.3.1, rule 3: "
298                             + elems[i]);
299                 }
300                 lengths[i] = null;
301             } else {
302                 remaining++;
303             }
304         }
305         if (remaining <= 1) {
306             return;
307         }
308         //Construct resolved space-specifier from the remaining spaces
309         int min = Integer.MIN_VALUE;
310         int max = Integer.MAX_VALUE;
311         for (int i = start; i <= end; i++) {
312             if (lengths[i] == null) {
313                 continue;
314             }
315             space = (SpaceElement)elems[i];
316             min = Math.max(min, space.getLength().getMin());
317             max = Math.min(max, space.getLength().getMax());
318             if (remaining > 1) {
319                 if (LOG.isDebugEnabled()) {
320                     LOG.debug("Nulling non-last space-specifier using 4.3.1, rule 3, second part: "
321                             + elems[i]);
322                 }
323                 lengths[i] = null;
324                 remaining--;
325             } else {
326                 lengths[i] = MinOptMax.getInstance(min, lengths[i].getOpt(), max);
327             }
328         }
329 
330         if (LOG.isTraceEnabled() && elems.length > 0) {
331             LOG.trace("Remaining spaces: " + remaining);
332             LOG.trace("-->Resulting list: " + toString(elems, lengths));
333         }
334     }
335 
performSpaceResolutionRules2to3(UnresolvedListElement[] elems, MinOptMax[] lengths)336     private void performSpaceResolutionRules2to3(UnresolvedListElement[] elems,
337             MinOptMax[] lengths) {
338         int start = 0;
339         int i = start;
340         while (i < elems.length) {
341             if (elems[i] instanceof SpaceElement) {
342                 while (i < elems.length) {
343                     if (elems[i] == null || elems[i] instanceof SpaceElement) {
344                         i++;
345                     } else {
346                         break;
347                     }
348                 }
349                 performSpaceResolutionRules2to3(elems, lengths, start, i - 1);
350             }
351             i++;
352             start = i;
353         }
354     }
355 
hasFirstPart()356     private boolean hasFirstPart() {
357         return firstPart != null && firstPart.length > 0;
358     }
359 
hasSecondPart()360     private boolean hasSecondPart() {
361         return secondPart != null && secondPart.length > 0;
362     }
363 
resolve()364     private void resolve() {
365         if (breakPoss != null) {
366             if (hasFirstPart()) {
367                 removeConditionalBorderAndPadding(firstPart, firstPartLengths, true);
368                 performSpaceResolutionRule1(firstPart, firstPartLengths, true);
369                 performSpaceResolutionRules2to3(firstPart, firstPartLengths);
370             }
371             if (hasSecondPart()) {
372                 removeConditionalBorderAndPadding(secondPart, secondPartLengths, false);
373                 performSpaceResolutionRule1(secondPart, secondPartLengths, false);
374                 performSpaceResolutionRules2to3(secondPart, secondPartLengths);
375             }
376             if (noBreak != null) {
377                 performSpaceResolutionRules2to3(noBreak, noBreakLengths);
378             }
379         } else {
380             if (isFirst) {
381                 removeConditionalBorderAndPadding(secondPart, secondPartLengths, false);
382                 performSpaceResolutionRule1(secondPart, secondPartLengths, false);
383             }
384             if (isLast) {
385                 removeConditionalBorderAndPadding(firstPart, firstPartLengths, true);
386                 performSpaceResolutionRule1(firstPart, firstPartLengths, true);
387             }
388 
389             if (hasFirstPart()) {
390                 //Now that we've handled isFirst/isLast conditions, we need to look at the
391                 //active part in its normal order so swap it back.
392                 LOG.trace("Swapping first and second parts.");
393                 UnresolvedListElementWithLength[] tempList;
394                 MinOptMax[] tempLengths;
395                 tempList = secondPart;
396                 tempLengths = secondPartLengths;
397                 secondPart = firstPart;
398                 secondPartLengths = firstPartLengths;
399                 firstPart = tempList;
400                 firstPartLengths = tempLengths;
401                 if (hasFirstPart()) {
402                     throw new IllegalStateException("Didn't expect more than one parts in a"
403                             + "no-break condition.");
404                 }
405             }
406             performSpaceResolutionRules2to3(secondPart, secondPartLengths);
407         }
408     }
409 
sum(MinOptMax[] lengths)410     private MinOptMax sum(MinOptMax[] lengths) {
411         MinOptMax sum = MinOptMax.ZERO;
412         for (MinOptMax length : lengths) {
413             if (length != null) {
414                 sum = sum.plus(length);
415             }
416         }
417         return sum;
418     }
419 
generate(ListIterator iter)420     private void generate(ListIterator iter) {
421         MinOptMax spaceBeforeBreak = sum(firstPartLengths);
422         MinOptMax spaceAfterBreak = sum(secondPartLengths);
423 
424         boolean hasPrecedingNonBlock = false;
425         if (breakPoss != null) {
426             if (spaceBeforeBreak.isNonZero()) {
427                 iter.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, null, true));
428                 iter.add(new KnuthGlue(spaceBeforeBreak, null, true));
429                 if (breakPoss.isForcedBreak()) {
430                     //Otherwise, the preceding penalty and glue will be cut off
431                     iter.add(new KnuthBox(0, null, true));
432                 }
433             }
434             iter.add(new KnuthPenalty(breakPoss.getPenaltyWidth(), breakPoss.getPenaltyValue(),
435                     false, breakPoss.getBreakClass(),
436                     new SpaceHandlingBreakPosition(this, breakPoss), false));
437             if (breakPoss.getPenaltyValue() <= -KnuthPenalty.INFINITE) {
438                 return; //return early. Not necessary (even wrong) to add additional elements
439             }
440 
441             // No break
442             // TODO: We can't use a MinOptMax for glue2,
443             // because min <= opt <= max is not always true - why?
444             MinOptMax noBreakLength = sum(noBreakLengths);
445             MinOptMax spaceSum = spaceBeforeBreak.plus(spaceAfterBreak);
446             int glue2width = noBreakLength.getOpt() - spaceSum.getOpt();
447             int glue2stretch = noBreakLength.getStretch() - spaceSum.getStretch();
448             int glue2shrink = noBreakLength.getShrink() - spaceSum.getShrink();
449 
450             if (glue2width != 0 || glue2stretch != 0 || glue2shrink != 0) {
451                 iter.add(new KnuthGlue(glue2width, glue2stretch, glue2shrink, null, true));
452             }
453         } else {
454             if (spaceBeforeBreak.isNonZero()) {
455                 throw new IllegalStateException("spaceBeforeBreak should be 0 in this case");
456             }
457         }
458         Position pos = null;
459         if (breakPoss == null) {
460             pos = new SpaceHandlingPosition(this);
461         }
462         if (spaceAfterBreak.isNonZero() || pos != null) {
463             iter.add(new KnuthBox(0, pos, true));
464         }
465         if (spaceAfterBreak.isNonZero()) {
466             iter.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, null, true));
467             iter.add(new KnuthGlue(spaceAfterBreak, null, true));
468             hasPrecedingNonBlock = true;
469         }
470         if (isLast && hasPrecedingNonBlock) {
471             //Otherwise, the preceding penalty and glue will be cut off
472             iter.add(new KnuthBox(0, null, true));
473         }
474     }
475 
476     /**
477      * Position class for break possibilities. It is used to notify layout manager about the
478      * effective spaces and conditional lengths.
479      */
480     public static class SpaceHandlingBreakPosition extends Position {
481 
482         private SpaceResolver resolver;
483         private Position originalPosition;
484 
485         /**
486          * Main constructor.
487          * @param resolver the space resolver that provides the info about the actual situation
488          * @param breakPoss the original break possibility that creates this Position
489          */
SpaceHandlingBreakPosition(SpaceResolver resolver, BreakElement breakPoss)490         public SpaceHandlingBreakPosition(SpaceResolver resolver, BreakElement breakPoss) {
491             super(null);
492             this.resolver = resolver;
493             this.originalPosition = breakPoss.getPosition();
494             //Unpack since the SpaceHandlingBreakPosition is a non-wrapped Position, too
495             while (this.originalPosition instanceof NonLeafPosition) {
496                 this.originalPosition = this.originalPosition.getPosition();
497             }
498         }
499 
500         /** @return the space resolver */
getSpaceResolver()501         public SpaceResolver getSpaceResolver() {
502             return this.resolver;
503         }
504 
505         /**
506          * Notifies all affected layout managers about the current situation in the part to be
507          * handled for area generation.
508          * @param isBreakSituation true if this is a break situation.
509          * @param side defines to notify about the situation whether before or after the break.
510          *             May be null if isBreakSituation is null.
511          */
notifyBreakSituation(boolean isBreakSituation, RelSide side)512         public void notifyBreakSituation(boolean isBreakSituation, RelSide side) {
513             if (isBreakSituation) {
514                 if (RelSide.BEFORE == side) {
515                     for (int i = 0; i < resolver.secondPart.length; i++) {
516                         resolver.secondPart[i].notifyLayoutManager(resolver.secondPartLengths[i]);
517                     }
518                 } else {
519                     for (int i = 0; i < resolver.firstPart.length; i++) {
520                         resolver.firstPart[i].notifyLayoutManager(resolver.firstPartLengths[i]);
521                     }
522                 }
523             } else {
524                 for (int i = 0; i < resolver.noBreak.length; i++) {
525                     resolver.noBreak[i].notifyLayoutManager(resolver.noBreakLengths[i]);
526                 }
527             }
528         }
529 
530         /** {@inheritDoc} */
toString()531         public String toString() {
532             StringBuffer sb = new StringBuffer();
533             sb.append("SpaceHandlingBreakPosition(");
534             sb.append(this.originalPosition);
535             sb.append(")");
536             return sb.toString();
537         }
538 
539         /**
540          * @return the original Position instance set at the BreakElement that this Position was
541          *         created for.
542          */
getOriginalBreakPosition()543         public Position getOriginalBreakPosition() {
544             return this.originalPosition;
545         }
546 
547         /** {@inheritDoc} */
getPosition()548         public Position getPosition() {
549             return originalPosition;
550         }
551 
552     }
553 
554     /**
555      * Position class for no-break situations. It is used to notify layout manager about the
556      * effective spaces and conditional lengths.
557      */
558     public static class SpaceHandlingPosition extends Position {
559 
560         private SpaceResolver resolver;
561 
562         /**
563          * Main constructor.
564          * @param resolver the space resolver that provides the info about the actual situation
565          */
SpaceHandlingPosition(SpaceResolver resolver)566         public SpaceHandlingPosition(SpaceResolver resolver) {
567             super(null);
568             this.resolver = resolver;
569         }
570 
571         /** @return the space resolver */
getSpaceResolver()572         public SpaceResolver getSpaceResolver() {
573             return this.resolver;
574         }
575 
576         /**
577          * Notifies all affected layout managers about the current situation in the part to be
578          * handled for area generation.
579          */
notifySpaceSituation()580         public void notifySpaceSituation() {
581             if (resolver.breakPoss != null) {
582                 throw new IllegalStateException("Only applicable to no-break situations");
583             }
584             for (int i = 0; i < resolver.secondPart.length; i++) {
585                 resolver.secondPart[i].notifyLayoutManager(resolver.secondPartLengths[i]);
586             }
587         }
588 
589         /** {@inheritDoc} */
toString()590         public String toString() {
591             return "SpaceHandlingPosition";
592         }
593     }
594 
595     /**
596      * Resolves unresolved elements applying the space resolution rules defined in 4.3.1.
597      * @param elems the element list
598      */
resolveElementList(List elems)599     public static void resolveElementList(List elems) {
600         if (LOG.isTraceEnabled()) {
601             LOG.trace(elems);
602         }
603         boolean first = true;
604         boolean last = false;
605         boolean skipNextElement = false;
606         List unresolvedFirst = new java.util.ArrayList();
607         List unresolvedSecond = new java.util.ArrayList();
608         List currentGroup;
609         ListIterator iter = elems.listIterator();
610         while (iter.hasNext()) {
611             ListElement el = (ListElement)iter.next();
612             if (el.isUnresolvedElement()) {
613                 if (LOG.isTraceEnabled()) {
614                     LOG.trace("unresolved found: " + el + " " + first + "/" + last);
615                 }
616                 BreakElement breakPoss = null;
617                 //Clear temp lists
618                 unresolvedFirst.clear();
619                 unresolvedSecond.clear();
620                 //Collect groups
621                 if (el instanceof BreakElement) {
622                     breakPoss = (BreakElement)el;
623                     currentGroup = unresolvedSecond;
624                 } else {
625                     currentGroup = unresolvedFirst;
626                     currentGroup.add(el);
627                 }
628                 iter.remove();
629                 last = true;
630                 skipNextElement = true;
631                 while (iter.hasNext()) {
632                     el = (ListElement)iter.next();
633                     if (el instanceof BreakElement && breakPoss != null) {
634                         skipNextElement = false;
635                         last = false;
636                         break;
637                     } else if (currentGroup == unresolvedFirst && (el instanceof BreakElement)) {
638                         breakPoss = (BreakElement)el;
639                         iter.remove();
640                         currentGroup = unresolvedSecond;
641                     } else if (el.isUnresolvedElement()) {
642                         currentGroup.add(el);
643                         iter.remove();
644                     } else {
645                         last = false;
646                         break;
647                     }
648                 }
649                 //last = !iter.hasNext();
650                 if (breakPoss == null && unresolvedSecond.isEmpty() && !last) {
651                     LOG.trace("Swap first and second parts in no-break condition,"
652                             + " second part is empty.");
653                     //The first list is reversed, so swap if this shouldn't happen
654                     List swapList = unresolvedSecond;
655                     unresolvedSecond = unresolvedFirst;
656                     unresolvedFirst = swapList;
657                 }
658 
659                 LOG.debug("----start space resolution (first=" + first + ", last=" + last + ")...");
660                 SpaceResolver resolver = new SpaceResolver(
661                         unresolvedFirst, breakPoss, unresolvedSecond, first, last);
662                 if (!last) {
663                     iter.previous();
664                 }
665                 resolver.generate(iter);
666                 if (!last && skipNextElement) {
667                     iter.next();
668                 }
669                 LOG.debug("----end space resolution.");
670             }
671             first = false;
672         }
673     }
674 
675     /**
676      * Inspects an effective element list and notifies all layout managers about the state of
677      * the spaces and conditional lengths.
678      * @param effectiveList the effective element list
679      * @param startElementIndex index of the first element in the part to be processed
680      * @param endElementIndex index of the last element in the part to be processed
681      * @param prevBreak index of the the break possibility just before this part (used to
682      *                  identify a break condition, lastBreak &lt;= 0 represents a no-break condition)
683      */
performConditionalsNotification(List effectiveList, int startElementIndex, int endElementIndex, int prevBreak)684     public static void performConditionalsNotification(List effectiveList,
685             int startElementIndex, int endElementIndex, int prevBreak) {
686         KnuthElement el = null;
687         if (prevBreak > 0) {
688             el = (KnuthElement)effectiveList.get(prevBreak);
689         }
690         SpaceResolver.SpaceHandlingBreakPosition beforeBreak = null;
691         SpaceResolver.SpaceHandlingBreakPosition afterBreak = null;
692         if (el != null && el.isPenalty()) {
693             Position pos = el.getPosition();
694             if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
695                 beforeBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
696                 beforeBreak.notifyBreakSituation(true, RelSide.BEFORE);
697             }
698         }
699         el = endElementIndex > -1 ? (KnuthElement) effectiveList.get(endElementIndex) : null;
700         if (el != null && el.isPenalty()) {
701             Position pos = el.getPosition();
702             if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
703                 afterBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
704                 afterBreak.notifyBreakSituation(true, RelSide.AFTER);
705             }
706         }
707         for (int i = startElementIndex; i <= endElementIndex; i++) {
708             Position pos = ((KnuthElement)effectiveList.get(i)).getPosition();
709             if (pos instanceof SpaceResolver.SpaceHandlingPosition) {
710                 ((SpaceResolver.SpaceHandlingPosition)pos).notifySpaceSituation();
711             } else if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
712                 SpaceResolver.SpaceHandlingBreakPosition noBreak;
713                 noBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
714                 if (noBreak != beforeBreak && noBreak != afterBreak) {
715                     noBreak.notifyBreakSituation(false, null);
716                 }
717             }
718         }
719     }
720 
721 
722 }
723