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: FlowLayoutManager.java 1761021 2016-09-16 11:40:57Z ssteiner $ */
19 
20 package org.apache.fop.layoutmgr;
21 
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.Stack;
26 
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 
30 import org.apache.fop.area.Area;
31 import org.apache.fop.area.BlockParent;
32 import org.apache.fop.fo.pagination.Flow;
33 import org.apache.fop.util.ListUtil;
34 
35 /**
36  * LayoutManager for an fo:flow object.
37  * Its parent LM is the PageSequenceLayoutManager.
38  * This LM is responsible for getting columns of the appropriate size
39  * and filling them with block-level areas generated by its children.
40  * TODO Reintroduce emergency counter (generate error to avoid endless loop)
41  */
42 public class FlowLayoutManager extends BlockStackingLayoutManager {
43 
44     /**
45      * logging instance
46      */
47     private static Log log = LogFactory.getLog(FlowLayoutManager.class);
48 
49     /** Array of areas currently being filled stored by area class */
50     private final BlockParent[] currentAreas = new BlockParent[Area.CLASS_MAX];
51 
52     private boolean handlingFloat;
53 
54     /**
55      * This is the top level layout manager.
56      * It is created by the PageSequence FO.
57      * @param pslm parent PageSequenceLayoutManager object
58      * @param node Flow object
59      */
FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node)60     public FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node) {
61         super(node);
62         setGeneratesBlockArea(true);
63         setParent(pslm);
64     }
65 
66     /** {@inheritDoc} */
67     @Override
getNextKnuthElements(LayoutContext context, int alignment)68     public List getNextKnuthElements(LayoutContext context, int alignment) {
69         return getNextKnuthElements(context, alignment, null, null);
70     }
71 
72     /**
73      * Get a sequence of KnuthElements representing the content
74      * of the node assigned to the LM.
75      * @param context   the LayoutContext used to store layout information
76      * @param alignment the desired text alignment
77      * @param restartPosition   {@link Position} to restart from
78      * @param restartLM {@link LayoutManager} to restart from
79      * @return the list of KnuthElements
80      * @see LayoutManager#getNextKnuthElements(LayoutContext,int)
81      */
getNextKnuthElements(LayoutContext context, int alignment, Position restartPosition, LayoutManager restartLM)82     List getNextKnuthElements(LayoutContext context, int alignment,
83             Position restartPosition, LayoutManager restartLM) {
84 
85         List<ListElement> elements = new LinkedList<ListElement>();
86 
87         boolean isRestart = (restartPosition != null);
88         // always reset in case of restart (exception: see below)
89         boolean doReset = isRestart;
90         LayoutManager currentChildLM;
91         Stack<LayoutManager> lmStack = new Stack<LayoutManager>();
92         if (isRestart) {
93             currentChildLM = restartPosition.getLM();
94             if (currentChildLM == null) {
95                 throw new IllegalStateException("Cannot find layout manager to restart from");
96             }
97             if (restartLM != null && restartLM.getParent() == this) {
98                 currentChildLM = restartLM;
99             } else {
100                 while (currentChildLM.getParent() != this) {
101                     lmStack.push(currentChildLM);
102                     currentChildLM = currentChildLM.getParent();
103                 }
104                 doReset = false;
105             }
106             setCurrentChildLM(currentChildLM);
107         } else {
108             currentChildLM = getChildLM();
109         }
110 
111         while (currentChildLM != null) {
112             if (!isRestart || doReset) {
113                 if (doReset) {
114                     currentChildLM.reset(); // TODO won't work with forced breaks
115                 }
116                 if (addChildElements(elements, currentChildLM, context, alignment,
117                         null, null, null) != null) {
118                     return elements;
119                 }
120             } else {
121                 if (addChildElements(elements, currentChildLM, context, alignment, lmStack,
122                         restartPosition, restartLM) != null) {
123                     return elements;
124                 }
125                 // restarted; force reset as of next child
126                 doReset = true;
127             }
128             currentChildLM = getChildLM();
129         }
130 
131         SpaceResolver.resolveElementList(elements);
132         setFinished(true);
133 
134         assert !elements.isEmpty();
135         return elements;
136     }
137 
addChildElements(List<ListElement> elements, LayoutManager childLM, LayoutContext context, int alignment, Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM)138     private List<ListElement> addChildElements(List<ListElement> elements,
139             LayoutManager childLM, LayoutContext context, int alignment,
140             Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM) {
141         if (handleSpanChange(childLM, context)) {
142             SpaceResolver.resolveElementList(elements);
143             return elements;
144         }
145 
146         LayoutContext childLC = makeChildLayoutContext(context);
147         List<ListElement> childElements
148                 = getNextChildElements(childLM, context, childLC, alignment, lmStack,
149                     position, restartAtLM);
150         if (elements.isEmpty()) {
151             context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
152         }
153         if (!elements.isEmpty()
154                 && !ElementListUtils.startsWithForcedBreak(childElements)) {
155             addInBetweenBreak(elements, context, childLC);
156         }
157         context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
158 
159         elements.addAll(childElements);
160 
161         if (ElementListUtils.endsWithForcedBreak(elements)) {
162             // a descendant of this flow has break-before or break-after
163             if (childLM.isFinished() && !hasNextChildLM()) {
164                 setFinished(true);
165             }
166             SpaceResolver.resolveElementList(elements);
167             return elements;
168         }
169         return null;
170     }
171 
handleSpanChange(LayoutManager childLM, LayoutContext context)172     private boolean handleSpanChange(LayoutManager childLM, LayoutContext context) {
173         int span = EN_NONE;
174         int disableColumnBalancing = EN_FALSE;
175         if (childLM instanceof BlockLayoutManager) {
176             span = ((BlockLayoutManager)childLM).getBlockFO().getSpan();
177             disableColumnBalancing = ((BlockLayoutManager) childLM).getBlockFO()
178                     .getDisableColumnBalancing();
179         } else if (childLM instanceof BlockContainerLayoutManager) {
180             span = ((BlockContainerLayoutManager)childLM).getBlockContainerFO().getSpan();
181             disableColumnBalancing = ((BlockContainerLayoutManager) childLM).getBlockContainerFO()
182                     .getDisableColumnBalancing();
183         }
184 
185         int currentSpan = context.getCurrentSpan();
186         if (currentSpan != span) {
187             if (span == EN_ALL) {
188                 context.setDisableColumnBalancing(disableColumnBalancing);
189             }
190             log.debug("span change from " + currentSpan + " to " + span);
191             context.signalSpanChange(span);
192             return true;
193         } else {
194             return false;
195         }
196     }
197 
198     /**
199      * Overridden to take into account the current page-master's
200      * writing-mode
201      * {@inheritDoc}
202      */
203     @Override
makeChildLayoutContext(LayoutContext context)204     protected LayoutContext makeChildLayoutContext(LayoutContext context) {
205         LayoutContext childLC = LayoutContext.newInstance();
206         childLC.setStackLimitBP(context.getStackLimitBP());
207         childLC.setRefIPD(context.getRefIPD());
208         childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode());
209         return childLC;
210     }
211 
212     /**
213      * Overridden to wrap the child positions before returning the list
214      * {@inheritDoc}
215      */
216     @Override
getNextChildElements(LayoutManager childLM, LayoutContext context, LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack, Position restartPosition, LayoutManager restartLM)217     protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context,
218             LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack,
219             Position restartPosition, LayoutManager restartLM) {
220 
221         List<ListElement> childElements;
222         if (lmStack == null) {
223             childElements = childLM.getNextKnuthElements(childLC, alignment);
224         } else {
225             childElements = childLM.getNextKnuthElements(childLC, alignment,
226                     lmStack, restartPosition, restartLM);
227         }
228         assert !childElements.isEmpty();
229 
230         // "wrap" the Position inside each element
231         List tempList = childElements;
232         childElements = new LinkedList<ListElement>();
233         wrapPositionElements(tempList, childElements);
234         return childElements;
235     }
236 
237     /** {@inheritDoc} */
238     @Override
negotiateBPDAdjustment(int adj, KnuthElement lastElement)239     public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
240         log.debug(" FLM.negotiateBPDAdjustment> " + adj);
241 
242         Position lastPosition = lastElement.getPosition();
243         if (lastPosition instanceof NonLeafPosition) {
244             // this element was not created by this FlowLM
245             NonLeafPosition savedPos = (NonLeafPosition) lastPosition;
246             lastElement.setPosition(savedPos.getPosition());
247             int returnValue = ((BlockLevelLayoutManager)lastElement.getLayoutManager())
248                     .negotiateBPDAdjustment(adj, lastElement);
249             lastElement.setPosition(savedPos);
250             log.debug(" FLM.negotiateBPDAdjustment> result " + returnValue);
251             return returnValue;
252         } else {
253             return 0;
254         }
255     }
256 
257     /** {@inheritDoc} */
258     @Override
discardSpace(KnuthGlue spaceGlue)259     public void discardSpace(KnuthGlue spaceGlue) {
260         log.debug(" FLM.discardSpace> ");
261 
262         Position gluePosition = spaceGlue.getPosition();
263         if (gluePosition instanceof NonLeafPosition) {
264             // this element was not created by this FlowLM
265             NonLeafPosition savedPos = (NonLeafPosition) gluePosition;
266             spaceGlue.setPosition(savedPos.getPosition());
267             ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue);
268             spaceGlue.setPosition(savedPos);
269         }
270     }
271 
272     /** {@inheritDoc} */
273     @Override
getKeepTogether()274     public Keep getKeepTogether() {
275         return Keep.KEEP_AUTO;
276     }
277 
278     /** {@inheritDoc} */
279     @Override
getKeepWithNext()280     public Keep getKeepWithNext() {
281         return Keep.KEEP_AUTO;
282     }
283 
284     /** {@inheritDoc} */
285     @Override
getKeepWithPrevious()286     public Keep getKeepWithPrevious() {
287         return Keep.KEEP_AUTO;
288     }
289 
290     /** {@inheritDoc} */
291     @Override
getChangedKnuthElements(List oldList, int alignment)292     public List<KnuthElement> getChangedKnuthElements(List oldList, int alignment) {
293         ListIterator<KnuthElement> oldListIterator = oldList.listIterator();
294         KnuthElement returnedElement;
295         List<KnuthElement> returnedList = new LinkedList<KnuthElement>();
296         List<KnuthElement> returnList = new LinkedList<KnuthElement>();
297         KnuthElement prevElement = null;
298         KnuthElement currElement = null;
299         int fromIndex = 0;
300 
301         // "unwrap" the Positions stored in the elements
302         KnuthElement oldElement;
303         while (oldListIterator.hasNext()) {
304             oldElement = oldListIterator.next();
305             if (oldElement.getPosition() instanceof NonLeafPosition) {
306                 // oldElement was created by a descendant of this FlowLM
307                 oldElement.setPosition((oldElement.getPosition()).getPosition());
308             } else {
309                 // thisElement was created by this FlowLM, remove it
310                 oldListIterator.remove();
311             }
312         }
313         // reset the iterator
314         oldListIterator = oldList.listIterator();
315 
316 
317         while (oldListIterator.hasNext()) {
318             currElement = oldListIterator.next();
319             if (prevElement != null
320                 && prevElement.getLayoutManager() != currElement.getLayoutManager()) {
321                 // prevElement is the last element generated by the same LM
322                 BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager)
323                                                  prevElement.getLayoutManager();
324                 BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
325                                                  currElement.getLayoutManager();
326                 returnedList.addAll(prevLM.getChangedKnuthElements(
327                         oldList.subList(fromIndex, oldListIterator.previousIndex()), alignment));
328                 fromIndex = oldListIterator.previousIndex();
329 
330                 // there is another block after this one
331                 if (prevLM.mustKeepWithNext()
332                     || currLM.mustKeepWithPrevious()) {
333                     // add an infinite penalty to forbid a break between blocks
334                     returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
335                             new Position(this), false));
336                 } else if (!ListUtil.getLast(returnedList).isGlue()) {
337                     // add a null penalty to allow a break between blocks
338                     returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
339                 }
340             }
341             prevElement = currElement;
342         }
343         if (currElement != null) {
344             BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
345                                              currElement.getLayoutManager();
346             returnedList.addAll(currLM.getChangedKnuthElements(
347                     oldList.subList(fromIndex, oldList.size()), alignment));
348         }
349 
350         // "wrap" the Position stored in each element of returnedList
351         // and add elements to returnList
352         for (KnuthElement aReturnedList : returnedList) {
353             returnedElement = aReturnedList;
354             if (returnedElement.getLayoutManager() != this) {
355                 returnedElement.setPosition(
356                         new NonLeafPosition(this, returnedElement.getPosition()));
357             }
358             returnList.add(returnedElement);
359         }
360 
361         return returnList;
362     }
363 
364     /** {@inheritDoc} */
365     @Override
addAreas(PositionIterator parentIter, LayoutContext layoutContext)366     public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
367         AreaAdditionUtil.addAreas(this, parentIter, layoutContext);
368         flush();
369     }
370 
371     /**
372      * Add child area to a the correct container, depending on its
373      * area class. A Flow can fill at most one area container of any class
374      * at any one time. The actual work is done by BlockStackingLM.
375      *
376      * @param childArea the area to add
377      */
378     @Override
addChildArea(Area childArea)379     public void addChildArea(Area childArea) {
380         if (childArea instanceof BlockParent && handlingFloat()) {
381             BlockParent bp = (BlockParent) childArea;
382             bp.setXOffset(getPSLM().getStartIntrusionAdjustment());
383         }
384         getParentArea(childArea);
385         addChildToArea(childArea,
386                           this.currentAreas[childArea.getAreaClass()]);
387     }
388 
389     /** {@inheritDoc} */
390     @Override
getParentArea(Area childArea)391     public Area getParentArea(Area childArea) {
392         BlockParent parentArea = null;
393         int aclass = childArea.getAreaClass();
394 
395         if (aclass == Area.CLASS_NORMAL || aclass == Area.CLASS_SIDE_FLOAT) {
396             parentArea = getCurrentPV().getCurrentFlow();
397         } else if (aclass == Area.CLASS_BEFORE_FLOAT) {
398             parentArea = getCurrentPV().getBodyRegion().getBeforeFloat();
399         } else if (aclass == Area.CLASS_FOOTNOTE) {
400             parentArea = getCurrentPV().getBodyRegion().getFootnote();
401         } else {
402             throw new IllegalStateException("(internal error) Invalid "
403                     + "area class (" + aclass + ") requested.");
404         }
405 
406         this.currentAreas[aclass] = parentArea;
407         setCurrentArea(parentArea);
408         return parentArea;
409     }
410 
411     /**
412      * Returns the IPD of the content area
413      * @return the IPD of the content area
414      */
415     @Override
getContentAreaIPD()416     public int getContentAreaIPD() {
417         int flowIPD = getPSLM().getCurrentColumnWidth();
418         return flowIPD;
419     }
420 
421     /**
422      * Returns the BPD of the content area
423      * @return the BPD of the content area
424      */
425     @Override
getContentAreaBPD()426     public int getContentAreaBPD() {
427         return getCurrentPV().getBodyRegion().getBPD();
428     }
429 
430     /** {@inheritDoc} */
431     @Override
isRestartable()432     public boolean isRestartable() {
433         return true;
434     }
435 
handleFloatOn()436     public void handleFloatOn() {
437         handlingFloat = true;
438     }
439 
handleFloatOff()440     public void handleFloatOff() {
441         handlingFloat = false;
442     }
443 
handlingFloat()444     public boolean handlingFloat() {
445         return handlingFloat;
446     }
447 }
448 
449