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: BlockContainerLayoutManager.java 1877372 2020-05-05 08:12:02Z ssteiner $ */
19 
20 package org.apache.fop.layoutmgr;
21 
22 import java.awt.Point;
23 import java.awt.geom.Rectangle2D;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Stack;
27 
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 
31 import org.apache.fop.area.Area;
32 import org.apache.fop.area.Block;
33 import org.apache.fop.area.BlockViewport;
34 import org.apache.fop.area.CTM;
35 import org.apache.fop.area.Trait;
36 import org.apache.fop.datatypes.FODimension;
37 import org.apache.fop.datatypes.Length;
38 import org.apache.fop.fo.flow.BlockContainer;
39 import org.apache.fop.fo.properties.CommonAbsolutePosition;
40 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
41 import org.apache.fop.fo.properties.KeepProperty;
42 import org.apache.fop.traits.MinOptMax;
43 import org.apache.fop.traits.SpaceVal;
44 
45 /**
46  * LayoutManager for a block-container FO.
47  */
48 public class BlockContainerLayoutManager extends SpacedBorderedPaddedBlockLayoutManager
49         implements BreakOpportunity {
50 
51     /**
52      * logging instance
53      */
54     private static Log log = LogFactory.getLog(BlockContainerLayoutManager.class);
55 
56     private BlockViewport viewportBlockArea;
57     private Block referenceArea;
58 
59     private CommonAbsolutePosition abProps;
60     private FODimension relDims;
61     private CTM absoluteCTM;
62     private Length width;
63     private Length height;
64     //private int vpContentIPD;
65     private int vpContentBPD;
66 
67     // When viewport should grow with the content.
68     private boolean autoHeight = true;
69     private boolean inlineElementList;
70 
71     /* holds the (one-time use) fo:block space-before
72     and -after properties.  Large fo:blocks are split
73     into multiple Area.Blocks to accomodate the subsequent
74     regions (pages) they are placed on.  space-before
75     is applied at the beginning of the first
76     Block and space-after at the end of the last Block
77     used in rendering the fo:block.
78     */
79     //TODO space-before|after: handle space-resolution rules
80     private MinOptMax foBlockSpaceBefore;
81     private MinOptMax foBlockSpaceAfter;
82 
83     private int horizontalOverflow;
84     private double contentRectOffsetX;
85     private double contentRectOffsetY;
86 
87     /**
88      * Create a new block container layout manager.
89      * @param node block-container node to create the layout manager for.
90      */
BlockContainerLayoutManager(BlockContainer node)91     public BlockContainerLayoutManager(BlockContainer node) {
92         super(node);
93         setGeneratesBlockArea(true);
94     }
95 
96     /** {@inheritDoc} */
97     @Override
initialize()98     public void initialize() {
99         abProps = getBlockContainerFO().getCommonAbsolutePosition();
100         foBlockSpaceBefore = new SpaceVal(getBlockContainerFO().getCommonMarginBlock()
101                     .spaceBefore, this).getSpace();
102         foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock()
103                     .spaceAfter, this).getSpace();
104         startIndent = getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(this);
105         endIndent = getBlockContainerFO().getCommonMarginBlock().endIndent.getValue(this);
106 
107         if (blockProgressionDirectionChanges()) {
108             height = getBlockContainerFO().getInlineProgressionDimension()
109                             .getOptimum(this).getLength();
110             width = getBlockContainerFO().getBlockProgressionDimension()
111                             .getOptimum(this).getLength();
112         } else {
113             height = getBlockContainerFO().getBlockProgressionDimension()
114                             .getOptimum(this).getLength();
115             width = getBlockContainerFO().getInlineProgressionDimension()
116                             .getOptimum(this).getLength();
117         }
118 
119         // use optimum space values
120         adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock()
121             .spaceBefore.getSpace().getOptimum(this).getLength().getValue(this);
122         adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock()
123             .spaceAfter.getSpace().getOptimum(this).getLength().getValue(this);
124     }
125 
126     @Override
getCommonBorderPaddingBackground()127     protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
128         return getBlockContainerFO().getCommonBorderPaddingBackground();
129     }
130 
resetSpaces()131     private void resetSpaces() {
132         this.discardBorderBefore = false;
133         this.discardBorderAfter = false;
134         this.discardPaddingBefore = false;
135         this.discardPaddingAfter = false;
136         this.effSpaceBefore = null;
137         this.effSpaceAfter = null;
138     }
139 
140     /** @return the content IPD */
getRotatedIPD()141     protected int getRotatedIPD() {
142         return getBlockContainerFO().getInlineProgressionDimension()
143                 .getOptimum(this).getLength().getValue(this);
144     }
145 
needClip()146     private boolean needClip() {
147         int overflow = getBlockContainerFO().getOverflow();
148         return (overflow == EN_HIDDEN || overflow == EN_ERROR_IF_OVERFLOW);
149     }
150 
getBPIndents()151     private int getBPIndents() {
152         int indents = 0;
153         /* TODO This is wrong isn't it?
154         indents += getBlockContainerFO().getCommonMarginBlock()
155                     .spaceBefore.getOptimum(this).getLength().getValue(this);
156         indents += getBlockContainerFO().getCommonMarginBlock()
157                     .spaceAfter.getOptimum(this).getLength().getValue(this);
158         */
159         indents += getBlockContainerFO().getCommonBorderPaddingBackground()
160                     .getBPPaddingAndBorder(false, this);
161         return indents;
162     }
163 
isAbsoluteOrFixed()164     protected boolean isAbsoluteOrFixed() {
165         return (abProps.absolutePosition == EN_ABSOLUTE
166                 || abProps.absolutePosition == EN_FIXED);
167     }
168 
isFixed()169     private boolean isFixed() {
170         return (abProps.absolutePosition == EN_FIXED);
171     }
172 
173     /** {@inheritDoc} */
174     @Override
getContentAreaBPD()175     public int getContentAreaBPD() {
176         if (autoHeight) {
177             return -1;
178         } else {
179             return this.vpContentBPD;
180         }
181     }
182 
183     /** {@inheritDoc} */
184     @Override
getNextKnuthElements(LayoutContext context, int alignment)185     public List getNextKnuthElements(LayoutContext context, int alignment) {
186         return getNextKnuthElements(context, alignment, null, null, null);
187     }
188 
189     /**
190      * Overridden to handle writing-mode, and different stack limit
191      * setup.
192      * {@inheritDoc}
193      */
194     @Override
makeChildLayoutContext(LayoutContext context)195     protected LayoutContext makeChildLayoutContext(LayoutContext context) {
196         LayoutContext childLC = LayoutContext.newInstance();
197         childLC.setStackLimitBP(
198                 context.getStackLimitBP().minus(MinOptMax.getInstance(relDims.bpd)));
199         childLC.setRefIPD(relDims.ipd);
200         childLC.setWritingMode(getBlockContainerFO().getWritingMode());
201         return childLC;
202     }
203 
204     /** {@inheritDoc} */
205     @Override
getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM)206     public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
207         Position restartPosition, LayoutManager restartAtLM) {
208 
209         resetSpaces();
210         // special treatment for position="absolute|fixed"
211         if (isAbsoluteOrFixed()) {
212             return getNextKnuthElementsAbsolute(context);
213         }
214 
215         boolean isRestart = (lmStack != null);
216         boolean emptyStack = (!isRestart || lmStack.isEmpty());
217 
218         setupAreaDimensions(context);
219 
220         List<ListElement> returnedList;
221         List<ListElement> contentList = new LinkedList<ListElement>();
222         List<ListElement> returnList = new LinkedList<ListElement>();
223 
224         if (!breakBeforeServed(context, returnList)) {
225             return returnList;
226         }
227 
228         addFirstVisibleMarks(returnList, context, alignment);
229 
230         if (autoHeight && inlineElementList) {
231 
232             LayoutManager curLM; // currently active LM
233             LayoutManager prevLM = null; // previously active LM
234 
235             LayoutContext childLC;
236             if (isRestart) {
237                 if (emptyStack) {
238                     assert restartAtLM != null && restartAtLM.getParent() == this;
239                     curLM = restartAtLM;
240                 } else {
241                     curLM = (LayoutManager) lmStack.pop();
242                 }
243                 setCurrentChildLM(curLM);
244             } else {
245                 curLM = getChildLM();
246             }
247 
248             while (curLM != null) {
249                 childLC = makeChildLayoutContext(context);
250 
251                 // get elements from curLM
252                 if (!isRestart || emptyStack) {
253                     if (isRestart) {
254                         curLM.reset();
255                     }
256                     returnedList = getNextChildElements(curLM, context, childLC, alignment,
257                             null, null, null);
258                 } else {
259                     returnedList = getNextChildElements(curLM, context, childLC, alignment,
260                             lmStack, restartPosition, restartAtLM);
261                     // once encountered, irrelevant for following child LMs
262                     emptyStack = true;
263                 }
264                 if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) {
265                     //Propagate keep-with-previous up from the first child
266                     context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
267                     childLC.clearKeepWithPreviousPending();
268                 }
269                 if (returnedList.size() == 1
270                         && ElementListUtils.startsWithForcedBreak(returnedList)) {
271                     // a descendant of this block has break-before
272                     contentList.addAll(returnedList);
273 
274                     // "wrap" the Position inside each element
275                     // moving the elements from contentList to returnList
276                     wrapPositionElements(contentList, returnList);
277 
278                     return returnList;
279                 } else {
280                     if (prevLM != null) {
281                         // there is a block handled by prevLM
282                         // before the one handled by curLM
283                         addInBetweenBreak(contentList, context, childLC);
284                     }
285                     contentList.addAll(returnedList);
286                     if (returnedList.isEmpty()) {
287                         //Avoid NoSuchElementException below (happens with empty blocks)
288                         continue;
289                     }
290                     if (ElementListUtils.endsWithForcedBreak(returnedList)) {
291                         // a descendant of this block has break-after
292                         if (curLM.isFinished() && !hasNextChildLM()) {
293                             // there is no other content in this block;
294                             // it's useless to add space after before a page break
295                             setFinished(true);
296                         }
297 
298                         wrapPositionElements(contentList, returnList);
299                         return returnList;
300                     }
301                 }
302                 // propagate and clear
303                 context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
304                 childLC.clearKeepsPending();
305                 prevLM = curLM;
306                 curLM = getChildLM();
307             }
308             wrapPositionElements(contentList, returnList);
309         } else {
310             returnList.add(generateNonInlinedBox());
311         }
312 
313         addLastVisibleMarks(returnList, context, alignment);
314 
315         addKnuthElementsForBreakAfter(returnList, context);
316 
317         context.updateKeepWithNextPending(getKeepWithNext());
318 
319         setFinished(true);
320         return returnList;
321     }
322 
setupAreaDimensions(LayoutContext context)323     private void setupAreaDimensions(LayoutContext context) {
324         autoHeight = false;
325         int maxbpd = context.getStackLimitBP().getOpt();
326         int allocBPD;
327         BlockContainer fo = getBlockContainerFO();
328         if (height.getEnum() == EN_AUTO
329                 || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) {
330             //auto height when height="auto" or "if that dimension is not specified explicitly
331             //(i.e., it depends on content's block-progression-dimension)" (XSL 1.0, 7.14.1)
332             allocBPD = maxbpd;
333             autoHeight = true;
334             //Cannot easily inline element list when ref-or<>"0"
335             inlineElementList = (fo.getReferenceOrientation() == 0);
336         } else {
337             allocBPD = height.getValue(this); //this is the content-height
338             allocBPD += getBPIndents();
339         }
340         vpContentBPD = allocBPD - getBPIndents();
341 
342         referenceIPD = context.getRefIPD();
343         if (width.getEnum() == EN_AUTO) {
344             updateContentAreaIPDwithOverconstrainedAdjust();
345         } else {
346             int contentWidth = width.getValue(this);
347             updateContentAreaIPDwithOverconstrainedAdjust(contentWidth);
348         }
349 
350         contentRectOffsetX = 0;
351         contentRectOffsetY = 0;
352 
353         int level = fo.getBidiLevel();
354         if ((level < 0) || ((level & 1) == 0)) {
355             contentRectOffsetX += fo.getCommonMarginBlock().startIndent.getValue(this);
356         } else {
357             contentRectOffsetX += fo.getCommonMarginBlock().endIndent.getValue(this);
358         }
359         contentRectOffsetY += fo.getCommonBorderPaddingBackground().getBorderBeforeWidth(false);
360         contentRectOffsetY += fo.getCommonBorderPaddingBackground().getPaddingBefore(false, this);
361 
362         updateRelDims();
363 
364         int availableIPD = referenceIPD - getIPIndents();
365         if (getContentAreaIPD() > availableIPD) {
366             BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
367                     fo.getUserAgent().getEventBroadcaster());
368             eventProducer.objectTooWide(this, fo.getName(),
369                     getContentAreaIPD(), context.getRefIPD(),
370                     fo.getLocator());
371         }
372     }
373 
generateNonInlinedBox()374     private KnuthBox generateNonInlinedBox() {
375 
376         MinOptMax range = MinOptMax.getInstance(relDims.ipd);
377         BlockContainerBreaker breaker = new BlockContainerBreaker(this, range);
378         breaker.doLayout(relDims.bpd, autoHeight);
379         boolean contentOverflows = breaker.isOverflow();
380         if (autoHeight) {
381             //Update content BPD now that it is known
382             int newHeight = breaker.deferredAlg.totalWidth;
383             if (blockProgressionDirectionChanges()) {
384                 setContentAreaIPD(newHeight);
385             } else {
386                 vpContentBPD = newHeight;
387             }
388             updateRelDims();
389         }
390 
391         Position bcPosition = new BlockContainerPosition(this, breaker);
392         KnuthBox knuthBox = new KnuthBox(vpContentBPD, notifyPos(bcPosition), false);
393         //TODO Handle min/opt/max for block-progression-dimension
394         /* These two elements will be used to add stretchability to the above box
395         returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE,
396                                false, returnPosition, false));
397         returnList.add(new KnuthGlue(0, 1 * constantLineHeight, 0,
398                                LINE_NUMBER_ADJUSTMENT, returnPosition, false));
399         */
400 
401         if (contentOverflows) {
402             BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
403                     getBlockContainerFO().getUserAgent().getEventBroadcaster());
404             boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
405             eventProducer.viewportBPDOverflow(this, getBlockContainerFO().getName(),
406                     breaker.getOverflowAmount(), needClip(), canRecover,
407                     getBlockContainerFO().getLocator());
408         }
409         return knuthBox;
410     }
411 
blockProgressionDirectionChanges()412     private boolean blockProgressionDirectionChanges() {
413         return getBlockContainerFO().getReferenceOrientation() % 180 != 0;
414     }
415 
416     /** {@inheritDoc} */
417     @Override
isRestartable()418     public boolean isRestartable() {
419         return true;
420     }
421 
getNextKnuthElementsAbsolute(LayoutContext context)422     private List<ListElement> getNextKnuthElementsAbsolute(LayoutContext context) {
423         autoHeight = false;
424 
425         boolean bpDirectionChanges = blockProgressionDirectionChanges();
426         Point offset = getAbsOffset();
427         int allocBPD;
428         int allocIPD;
429         if (height.getEnum() == EN_AUTO
430                 || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) {
431             //auto height when height="auto" or "if that dimension is not specified explicitly
432             //(i.e., it depends on content's blockprogression-dimension)" (XSL 1.0, 7.14.1)
433             allocBPD = 0;
434             if (abProps.bottom.getEnum() != EN_AUTO) {
435                 int availHeight;
436                 if (isFixed()) {
437                     availHeight = (int)getCurrentPV().getViewArea().getHeight();
438                 } else {
439                     availHeight = context.getStackLimitBP().getOpt();
440                 }
441                 allocBPD = availHeight;
442                 allocBPD -= offset.y;
443                 if (abProps.bottom.getEnum() != EN_AUTO) {
444                     allocBPD -= abProps.bottom.getValue(this);
445                     if (allocBPD < 0) {
446                         //TODO Fix absolute b-c layout, layout may need to be defferred until
447                         //after page breaking when the size of the containing box is known.
448                         /* Warning disabled due to a interpretation mistake.
449                          * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
450                         log.error("The current combination of top and bottom properties results"
451                                 + " in a negative extent for the block-container. 'bottom' may be"
452                                 + " at most " + (allocBPD + abProps.bottom.getValue(this)) + " mpt,"
453                                 + " but was actually " + abProps.bottom.getValue(this) + " mpt."
454                                 + " The nominal available height is " + availHeight + " mpt.");
455                         */
456                         allocBPD = 0;
457                     }
458                 } else {
459                     if (allocBPD < 0) {
460                         /* Warning disabled due to a interpretation mistake.
461                          * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
462                         log.error("The current combination of top and bottom properties results"
463                                 + " in a negative extent for the block-container. 'top' may be"
464                                 + " at most " + availHeight + " mpt,"
465                                 + " but was actually " + offset.y + " mpt."
466                                 + " The nominal available height is " + availHeight + " mpt.");
467                         */
468                         allocBPD = 0;
469                     }
470                 }
471             } else {
472                 allocBPD = context.getStackLimitBP().getOpt();
473                 if (!bpDirectionChanges) {
474                     autoHeight = true;
475                 }
476             }
477         } else {
478             allocBPD = height.getValue(this); //this is the content-height
479             allocBPD += getBPIndents();
480         }
481         if (width.getEnum() == EN_AUTO) {
482             int availWidth;
483             if (isFixed()) {
484                 availWidth = (int)getCurrentPV().getViewArea().getWidth();
485             } else {
486                 availWidth = context.getRefIPD();
487             }
488             allocIPD = availWidth;
489             if (abProps.left.getEnum() != EN_AUTO) {
490                 allocIPD -= abProps.left.getValue(this);
491             }
492             if (abProps.right.getEnum() != EN_AUTO) {
493                 allocIPD -= abProps.right.getValue(this);
494                 if (allocIPD < 0) {
495                     /* Warning disabled due to a interpretation mistake.
496                      * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
497                     log.error("The current combination of left and right properties results"
498                             + " in a negative extent for the block-container. 'right' may be"
499                             + " at most " + (allocIPD + abProps.right.getValue(this)) + " mpt,"
500                             + " but was actually " + abProps.right.getValue(this) + " mpt."
501                             + " The nominal available width is " + availWidth + " mpt.");
502                     */
503                     allocIPD = 0;
504                 }
505             } else {
506                 if (allocIPD < 0) {
507                     /* Warning disabled due to a interpretation mistake.
508                      * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
509                     log.error("The current combination of left and right properties results"
510                             + " in a negative extent for the block-container. 'left' may be"
511                             + " at most " + allocIPD + " mpt,"
512                             + " but was actually " + abProps.left.getValue(this) + " mpt."
513                             + " The nominal available width is " + availWidth + " mpt.");
514                     */
515                     allocIPD = 0;
516                 }
517                 if (bpDirectionChanges) {
518                     autoHeight = true;
519                 }
520             }
521         } else {
522             allocIPD = width.getValue(this); //this is the content-width
523             allocIPD += getIPIndents();
524         }
525 
526         vpContentBPD = allocBPD - getBPIndents();
527         setContentAreaIPD(allocIPD - getIPIndents());
528 
529         contentRectOffsetX = 0;
530         contentRectOffsetY = 0;
531         updateRelDims();
532 
533         MinOptMax range = MinOptMax.getInstance(relDims.ipd);
534         BlockContainerBreaker breaker = new BlockContainerBreaker(this, range);
535         breaker.doLayout((autoHeight ? 0 : relDims.bpd), autoHeight);
536         boolean contentOverflows = breaker.isOverflow();
537         if (autoHeight) {
538             //Update content BPD now that it is known
539             int newHeight = breaker.deferredAlg.totalWidth;
540             if (bpDirectionChanges) {
541                 setContentAreaIPD(newHeight);
542             } else {
543                 vpContentBPD = newHeight;
544             }
545             updateRelDims();
546         }
547         List<ListElement> returnList = new LinkedList<ListElement>();
548         if (!breaker.isEmpty()) {
549             Position bcPosition = new BlockContainerPosition(this, breaker);
550             returnList.add(new KnuthBox(0, notifyPos(bcPosition), false));
551 
552             //TODO Maybe check for page overflow when autoHeight=true
553             if (!autoHeight & (contentOverflows)) {
554                 BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
555                         getBlockContainerFO().getUserAgent().getEventBroadcaster());
556                 boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
557                 eventProducer.viewportBPDOverflow(this, getBlockContainerFO().getName(),
558                         breaker.getOverflowAmount(), needClip(), canRecover,
559                         getBlockContainerFO().getLocator());
560             }
561             // this handles the IPD (horizontal) overflow
562             if (this.horizontalOverflow > 0) {
563                 BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider
564                         .get(getBlockContainerFO().getUserAgent().getEventBroadcaster());
565                 boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
566                 eventProducer.viewportIPDOverflow(this, getBlockContainerFO().getName(),
567                         this.horizontalOverflow, needClip(), canRecover, getBlockContainerFO().getLocator());
568             }
569         }
570 
571         setFinished(true);
572         return returnList;
573     }
574 
updateRelDims()575     private void updateRelDims() {
576         Rectangle2D rect = new Rectangle2D.Double(
577                 contentRectOffsetX, contentRectOffsetY,
578                 getContentAreaIPD(),
579                 this.vpContentBPD);
580         relDims = new FODimension(0, 0);
581         absoluteCTM = CTM.getCTMandRelDims(
582                 getBlockContainerFO().getReferenceOrientation(),
583                 getBlockContainerFO().getWritingMode(),
584                 rect, relDims);
585     }
586 
587     private class BlockContainerPosition extends NonLeafPosition {
588 
589         private BlockContainerBreaker breaker;
590 
BlockContainerPosition(LayoutManager lm, BlockContainerBreaker breaker)591         public BlockContainerPosition(LayoutManager lm, BlockContainerBreaker breaker) {
592             super(lm, null);
593             this.breaker = breaker;
594         }
595 
getBreaker()596         public BlockContainerBreaker getBreaker() {
597             return this.breaker;
598         }
599 
600     }
601 
602     private class BlockContainerBreaker extends AbstractBreaker {
603 
604         private BlockContainerLayoutManager bclm;
605         private MinOptMax ipd;
606 
607         //Info for deferred adding of areas
608         private PageBreakingAlgorithm deferredAlg;
609         private BlockSequence deferredOriginalList;
610         private BlockSequence deferredEffectiveList;
611 
BlockContainerBreaker(BlockContainerLayoutManager bclm, MinOptMax ipd)612         public BlockContainerBreaker(BlockContainerLayoutManager bclm, MinOptMax ipd) {
613             this.bclm = bclm;
614             this.ipd = ipd;
615         }
616 
617         /** {@inheritDoc} */
observeElementList(List elementList)618         protected void observeElementList(List elementList) {
619             ElementListObserver.observe(elementList, "block-container",
620                     bclm.getBlockContainerFO().getId());
621         }
622 
623         /** {@inheritDoc} */
isPartOverflowRecoveryActivated()624         protected boolean isPartOverflowRecoveryActivated() {
625             //For block-containers, this must be disabled because of wanted overflow.
626             return false;
627         }
628 
629         /** {@inheritDoc} */
isSinglePartFavored()630         protected boolean isSinglePartFavored() {
631             return true;
632         }
633 
getDifferenceOfFirstPart()634         public int getDifferenceOfFirstPart() {
635             PageBreakPosition pbp = this.deferredAlg.getPageBreaks().getFirst();
636             return pbp.difference;
637         }
638 
isOverflow()639         public boolean isOverflow() {
640             return !isEmpty()
641                     && ((deferredAlg.getPageBreaks().size() > 1)
642                         || (deferredAlg.totalWidth - deferredAlg.totalShrink)
643                             > deferredAlg.getLineWidth());
644         }
645 
getOverflowAmount()646         public int getOverflowAmount() {
647             return (deferredAlg.totalWidth - deferredAlg.totalShrink)
648                 - deferredAlg.getLineWidth();
649         }
650 
getTopLevelLM()651         protected LayoutManager getTopLevelLM() {
652             return bclm;
653         }
654 
createLayoutContext()655         protected LayoutContext createLayoutContext() {
656             LayoutContext lc = super.createLayoutContext();
657             lc.setRefIPD(ipd.getOpt());
658             lc.setWritingMode(getBlockContainerFO().getWritingMode());
659             return lc;
660         }
661 
getNextKnuthElements(LayoutContext context, int alignment)662         protected List getNextKnuthElements(LayoutContext context, int alignment) {
663             LayoutManager curLM; // currently active LM
664             List<ListElement> returnList = new LinkedList<ListElement>();
665 
666             while ((curLM = getChildLM()) != null) {
667                 LayoutContext childLC = makeChildLayoutContext(context);
668 
669                 List returnedList = null;
670                 if (!curLM.isFinished()) {
671                     returnedList = curLM.getNextKnuthElements(childLC, alignment);
672                 }
673                 if (returnedList != null) {
674                     bclm.wrapPositionElements(returnedList, returnList);
675                 }
676             }
677             SpaceResolver.resolveElementList(returnList);
678             setFinished(true);
679             return returnList;
680         }
681 
getCurrentDisplayAlign()682         protected int getCurrentDisplayAlign() {
683             return getBlockContainerFO().getDisplayAlign();
684         }
685 
hasMoreContent()686         protected boolean hasMoreContent() {
687             return !isFinished();
688         }
689 
addAreas(PositionIterator posIter, LayoutContext context)690         protected void addAreas(PositionIterator posIter, LayoutContext context) {
691             AreaAdditionUtil.addAreas(bclm, posIter, context);
692         }
693 
doPhase3(PageBreakingAlgorithm alg, int partCount, BlockSequence originalList, BlockSequence effectiveList)694         protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
695                 BlockSequence originalList, BlockSequence effectiveList) {
696             //Defer adding of areas until addAreas is called by the parent LM
697             this.deferredAlg = alg;
698             this.deferredOriginalList = originalList;
699             this.deferredEffectiveList = effectiveList;
700         }
701 
finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp)702         protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) {
703             //nop for bclm
704         }
705 
getCurrentChildLM()706         protected LayoutManager getCurrentChildLM() {
707             return curChildLM;
708         }
709 
addContainedAreas(LayoutContext layoutContext)710         public void addContainedAreas(LayoutContext layoutContext) {
711             if (isEmpty()) {
712                 return;
713             }
714             //Rendering all parts (not just the first) at once for the case where the parts that
715             //overflow should be visible.
716             this.deferredAlg.removeAllPageBreaks();
717             this.addAreas(this.deferredAlg,
718                           0,
719                           this.deferredAlg.getPageBreaks().size(),
720                           this.deferredOriginalList, this.deferredEffectiveList,
721                           LayoutContext.offspringOf(layoutContext));
722         }
723 
724     }
725 
getAbsOffset()726     private Point getAbsOffset() {
727         int x = 0;
728         int y = 0;
729         if (abProps.left.getEnum() != EN_AUTO) {
730             x = abProps.left.getValue(this);
731         } else if (abProps.right.getEnum() != EN_AUTO
732                 && width.getEnum() != EN_AUTO) {
733             x = getReferenceAreaIPD()
734                 - abProps.right.getValue(this) - width.getValue(this);
735         }
736         if (abProps.top.getEnum() != EN_AUTO) {
737             y = abProps.top.getValue(this);
738         } else if (abProps.bottom.getEnum() != EN_AUTO
739                 && height.getEnum() != EN_AUTO) {
740             y = getReferenceAreaBPD()
741                 - abProps.bottom.getValue(this) - height.getValue(this);
742         }
743         return new Point(x, y);
744     }
745 
746     /** {@inheritDoc} */
747     @Override
addAreas(PositionIterator parentIter, LayoutContext layoutContext)748     public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
749         getParentArea(null);
750 
751         // if this will create the first block area in a page
752         // and display-align is bottom or center, add space before
753         if (layoutContext.getSpaceBefore() > 0) {
754             addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore()));
755         }
756 
757         LayoutManager childLM;
758         LayoutManager lastLM = null;
759         LayoutContext lc = LayoutContext.offspringOf(layoutContext);
760         lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
761         // set space after in the LayoutContext for children
762         if (layoutContext.getSpaceAfter() > 0) {
763             lc.setSpaceAfter(layoutContext.getSpaceAfter());
764         }
765         BlockContainerPosition bcpos = null;
766         PositionIterator childPosIter;
767 
768         // "unwrap" the NonLeafPositions stored in parentIter
769         // and put them in a new list;
770         List<Position> positionList = new LinkedList<Position>();
771         Position pos;
772         Position firstPos = null;
773         Position lastPos = null;
774         while (parentIter.hasNext()) {
775             pos = parentIter.next();
776             if (pos.getIndex() >= 0) {
777                 if (firstPos == null) {
778                     firstPos = pos;
779                 }
780                 lastPos = pos;
781             }
782             Position innerPosition = pos;
783             if (pos instanceof NonLeafPosition) {
784                 innerPosition = pos.getPosition();
785             }
786             if (pos instanceof BlockContainerPosition) {
787                 if (bcpos != null) {
788                     throw new IllegalStateException("Only one BlockContainerPosition allowed");
789                 }
790                 bcpos = (BlockContainerPosition)pos;
791                 //Add child areas inside the reference area
792                 //bcpos.getBreaker().addContainedAreas();
793             } else if (innerPosition == null) {
794                 //ignore (probably a Position for a simple penalty between blocks)
795             } else if (innerPosition.getLM() == this
796                     && !(innerPosition instanceof MappingPosition)) {
797                 // pos was created by this BlockLM and was inside a penalty
798                 // allowing or forbidding a page break
799                 // nothing to do
800             } else {
801                 // innerPosition was created by another LM
802                 positionList.add(innerPosition);
803                 lastLM = innerPosition.getLM();
804             }
805         }
806 
807         addId();
808 
809         registerMarkers(true, isFirst(firstPos), isLast(lastPos));
810 
811         if (bcpos == null) {
812             // the Positions in positionList were inside the elements
813             // created by the LineLM
814             childPosIter = new PositionIterator(positionList.listIterator());
815 
816             while ((childLM = childPosIter.getNextChildLM()) != null) {
817                 // set last area flag
818                 lc.setFlags(LayoutContext.LAST_AREA,
819                         (layoutContext.isLastArea() && childLM == lastLM));
820                 lc.setStackLimitBP(layoutContext.getStackLimitBP());
821                 // Add the line areas to Area
822                 childLM.addAreas(childPosIter, lc);
823             }
824         } else {
825             //Add child areas inside the reference area
826             bcpos.getBreaker().addContainedAreas(layoutContext);
827         }
828 
829         registerMarkers(false, isFirst(firstPos), isLast(lastPos));
830 
831         TraitSetter.addSpaceBeforeAfter(viewportBlockArea, layoutContext.getSpaceAdjust(),
832                 effSpaceBefore, effSpaceAfter);
833         flush();
834 
835         viewportBlockArea = null;
836         referenceArea = null;
837         resetSpaces();
838 
839         notifyEndOfLayout();
840     }
841 
842     /**
843      * Get the parent area for children of this block container.
844      * This returns the current block container area
845      * and creates it if required.
846      *
847      * {@inheritDoc}
848      */
849     @Override
getParentArea(Area childArea)850     public Area getParentArea(Area childArea) {
851         if (referenceArea == null) {
852             boolean switchedProgressionDirection = blockProgressionDirectionChanges();
853             boolean allowBPDUpdate = autoHeight && !switchedProgressionDirection;
854             int level = getBlockContainerFO().getBidiLevel();
855 
856             viewportBlockArea = new BlockViewport(allowBPDUpdate);
857             viewportBlockArea.setChangeBarList(getChangeBarList());
858             viewportBlockArea.addTrait(Trait.IS_VIEWPORT_AREA, Boolean.TRUE);
859             if (level >= 0) {
860                 viewportBlockArea.setBidiLevel(level);
861             }
862             viewportBlockArea.setIPD(getContentAreaIPD());
863             if (allowBPDUpdate) {
864                 viewportBlockArea.setBPD(0);
865             } else {
866                 viewportBlockArea.setBPD(this.vpContentBPD);
867             }
868             transferForeignAttributes(viewportBlockArea);
869 
870             TraitSetter.setProducerID(viewportBlockArea, getBlockContainerFO().getId());
871             TraitSetter.setLayer(viewportBlockArea, getBlockContainerFO().getLayer());
872             TraitSetter.addBorders(viewportBlockArea,
873                     getBlockContainerFO().getCommonBorderPaddingBackground(),
874                     discardBorderBefore, discardBorderAfter, false, false, this);
875             TraitSetter.addPadding(viewportBlockArea,
876                     getBlockContainerFO().getCommonBorderPaddingBackground(),
877                     discardPaddingBefore, discardPaddingAfter, false, false, this);
878             TraitSetter.addMargins(viewportBlockArea,
879                     getBlockContainerFO().getCommonBorderPaddingBackground(),
880                     startIndent, endIndent,
881                     this);
882 
883             viewportBlockArea.setCTM(absoluteCTM);
884             viewportBlockArea.setClip(needClip());
885 
886             if (abProps.absolutePosition == EN_ABSOLUTE
887                     || abProps.absolutePosition == EN_FIXED) {
888                 Point offset = getAbsOffset();
889                 viewportBlockArea.setXOffset(offset.x);
890                 viewportBlockArea.setYOffset(offset.y);
891             } else {
892                 //nop
893             }
894 
895             referenceArea = new Block();
896             referenceArea.setChangeBarList(getChangeBarList());
897             referenceArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
898             if (level >= 0) {
899                 referenceArea.setBidiLevel(level);
900             }
901             TraitSetter.setProducerID(referenceArea, getBlockContainerFO().getId());
902 
903             if (abProps.absolutePosition == EN_ABSOLUTE) {
904                 viewportBlockArea.setPositioning(Block.ABSOLUTE);
905             } else if (abProps.absolutePosition == EN_FIXED) {
906                 viewportBlockArea.setPositioning(Block.FIXED);
907             }
908 
909             // Set up dimensions
910             // Must get dimensions from parent area
911             /*Area parentArea =*/ parentLayoutManager.getParentArea(referenceArea);
912             //int referenceIPD = parentArea.getIPD();
913             referenceArea.setIPD(relDims.ipd);
914             // Get reference IPD from parentArea
915             setCurrentArea(viewportBlockArea); // ??? for generic operations
916         }
917         return referenceArea;
918     }
919 
920     /**
921      * Add the child to the block container.
922      *
923      * {@inheritDoc}
924      */
925     @Override
addChildArea(Area childArea)926     public void addChildArea(Area childArea) {
927         if (referenceArea != null) {
928             referenceArea.addBlock((Block) childArea);
929         }
930     }
931 
932     /**
933      * Force current area to be added to parent area.
934      * {@inheritDoc}
935      */
936     @Override
flush()937     protected void flush() {
938         viewportBlockArea.addBlock(referenceArea, autoHeight);
939 
940         TraitSetter.addBackground(viewportBlockArea,
941                 getBlockContainerFO().getCommonBorderPaddingBackground(),
942                 this);
943 
944         super.flush();
945     }
946 
947     /** {@inheritDoc} */
948     @Override
negotiateBPDAdjustment(int adj, KnuthElement lastElement)949     public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
950         // TODO Auto-generated method stub
951         return 0;
952     }
953 
954     /** {@inheritDoc} */
955     @Override
discardSpace(KnuthGlue spaceGlue)956     public void discardSpace(KnuthGlue spaceGlue) {
957         // TODO Auto-generated method stub
958     }
959 
960     /** {@inheritDoc} */
961     @Override
getKeepTogetherProperty()962     public KeepProperty getKeepTogetherProperty() {
963         return getBlockContainerFO().getKeepTogether();
964     }
965 
966     /** {@inheritDoc} */
967     @Override
getKeepWithPreviousProperty()968     public KeepProperty getKeepWithPreviousProperty() {
969         return getBlockContainerFO().getKeepWithPrevious();
970     }
971 
972     /** {@inheritDoc} */
973     @Override
getKeepWithNextProperty()974     public KeepProperty getKeepWithNextProperty() {
975         return getBlockContainerFO().getKeepWithNext();
976     }
977 
978     /**
979      * @return the BlockContainer node
980      */
getBlockContainerFO()981     protected BlockContainer getBlockContainerFO() {
982         return (BlockContainer) fobj;
983     }
984 
985     // --------- Property Resolution related functions --------- //
986 
987     /** {@inheritDoc} */
988     @Override
getGeneratesReferenceArea()989     public boolean getGeneratesReferenceArea() {
990         return true;
991     }
992 
993     /** {@inheritDoc} */
994     @Override
getGeneratesBlockArea()995     public boolean getGeneratesBlockArea() {
996         return true;
997     }
998 
999     /** {@inheritDoc} */
handleOverflow(int milliPoints)1000     public boolean handleOverflow(int milliPoints) {
1001         if (milliPoints > this.horizontalOverflow) {
1002             this.horizontalOverflow = milliPoints;
1003         }
1004         return true;
1005     }
1006 
1007 }
1008 
1009 
1010