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: BlockLayoutManager.java 1835810 2018-07-13 10:29: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.Block;
32 import org.apache.fop.area.LineArea;
33 import org.apache.fop.datatypes.Length;
34 import org.apache.fop.fo.FONode;
35 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
36 import org.apache.fop.fo.properties.KeepProperty;
37 import org.apache.fop.fonts.Font;
38 import org.apache.fop.fonts.FontInfo;
39 import org.apache.fop.fonts.FontTriplet;
40 import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager;
41 import org.apache.fop.layoutmgr.inline.LineLayoutManager;
42 import org.apache.fop.traits.MinOptMax;
43 import org.apache.fop.traits.SpaceVal;
44 
45 /**
46  * LayoutManager for a block FO.
47  */
48 public class BlockLayoutManager extends SpacedBorderedPaddedBlockLayoutManager
49         implements BreakOpportunity {
50 
51     /** logging instance */
52     private static Log log = LogFactory.getLog(BlockLayoutManager.class);
53 
54     private Block curBlockArea;
55 
56     /** Iterator over the child layout managers. */
57     protected ListIterator<LayoutManager> proxyLMiter;
58 
59     private int lead = 12000;
60     private Length lineHeight;
61     private int follow = 2000;
62     //private int middleShift = 0;
63 
64     /**
65      * Creates a new BlockLayoutManager.
66      * @param inBlock the block FO object to create the layout manager for.
67      */
BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock)68     public BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock) {
69         super(inBlock);
70         proxyLMiter = new ProxyLMiter();
71     }
72 
73     /** {@inheritDoc} */
74     @Override
initialize()75     public void initialize() {
76         super.initialize();
77         org.apache.fop.fo.flow.Block fo = getBlockFO();
78         FontInfo fi = fo.getFOEventHandler().getFontInfo();
79         FontTriplet[] fontkeys = fo.getCommonFont().getFontState(fi);
80         Font initFont = fi.getFontInstance(fontkeys[0],
81                 getBlockFO().getCommonFont().fontSize.getValue(this));
82         lead = initFont.getAscender();
83         follow = -initFont.getDescender();
84         //middleShift = -fs.getXHeight() / 2;
85         lineHeight = fo.getLineHeight().getOptimum(this).getLength();
86         startIndent = fo.getCommonMarginBlock().startIndent.getValue(this);
87         endIndent = fo.getCommonMarginBlock().endIndent.getValue(this);
88         foSpaceBefore = new SpaceVal(fo.getCommonMarginBlock().spaceBefore, this).getSpace();
89         foSpaceAfter = new SpaceVal(fo.getCommonMarginBlock().spaceAfter, this).getSpace();
90         // use optimum space values
91         adjustedSpaceBefore = fo.getCommonMarginBlock().spaceBefore.getSpace()
92                                     .getOptimum(this).getLength().getValue(this);
93         adjustedSpaceAfter = fo.getCommonMarginBlock().spaceAfter.getSpace()
94                                     .getOptimum(this).getLength().getValue(this);
95     }
96 
97     @Override
getCommonBorderPaddingBackground()98     protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
99         return getBlockFO().getCommonBorderPaddingBackground();
100     }
101 
102     /** {@inheritDoc} */
103     @Override
getNextKnuthElements(LayoutContext context, int alignment)104     public List getNextKnuthElements(LayoutContext context, int alignment) {
105         return getNextKnuthElements(context, alignment, null, null, null);
106     }
107 
108     /** {@inheritDoc} */
109     @Override
getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM)110     public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
111             Position restartPosition, LayoutManager restartAtLM) {
112         resetSpaces();
113         return super.getNextKnuthElements(
114                 context, alignment, lmStack, restartPosition, restartAtLM);
115     }
116 
117     /**
118      * Overridden to take into account that the childLM may be the block's
119      * {@link LineLayoutManager}.
120      * {@inheritDoc}
121      */
122     @Override
getNextChildElements(LayoutManager childLM, LayoutContext context, LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM)123     protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context,
124            LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition,
125            LayoutManager restartAtLM) {
126 
127         childLC.copyPendingMarksFrom(context);
128 
129         if (childLM instanceof LineLayoutManager) {
130             childLC.setRefIPD(getContentAreaIPD());
131         } else {
132             // nop; will have been properly set by makeChildLayoutContext()
133         }
134 
135         if (childLM == this.childLMs.get(0)) {
136             childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE);
137             //Handled already by the parent (break collapsing, see above)
138         }
139 
140         if (lmStack == null) {
141             return childLM.getNextKnuthElements(childLC, alignment);
142         } else {
143             if (childLM instanceof LineLayoutManager) {
144                 assert (restartPosition instanceof LeafPosition);
145                 return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment,
146                         (LeafPosition) restartPosition);
147             } else {
148                 return childLM.getNextKnuthElements(childLC, alignment,
149                         lmStack, restartPosition, restartAtLM);
150             }
151         }
152     }
153 
resetSpaces()154     private void resetSpaces() {
155         this.discardBorderBefore = false;
156         this.discardBorderAfter = false;
157         this.discardPaddingBefore = false;
158         this.discardPaddingAfter = false;
159         this.effSpaceBefore = null;
160         this.effSpaceAfter = null;
161     }
162 
163     /**
164      * Proxy iterator for Block LM.
165      * This iterator creates and holds the complete list
166      * of child LMs.
167      * It uses fobjIter as its base iterator.
168      * Block LM's createNextChildLMs uses this iterator
169      * as its base iterator.
170      */
171     protected class ProxyLMiter extends LMiter {
172 
173         /**
174          * Constructs a proxy iterator for Block LM.
175          */
ProxyLMiter()176         public ProxyLMiter() {
177             super(BlockLayoutManager.this);
178             listLMs = new java.util.ArrayList<LayoutManager>(10);
179         }
180 
181         /**
182          * @return true if there are more child lms
183          */
hasNext()184         public boolean hasNext() {
185             return (curPos < listLMs.size()) || createNextChildLMs(curPos);
186         }
187 
188         /**
189          * @param pos ...
190          * @return true if new child lms were added
191          */
createNextChildLMs(int pos)192         protected boolean createNextChildLMs(int pos) {
193             List<LayoutManager> newLMs = createChildLMs(pos + 1 - listLMs.size());
194             if (newLMs != null) {
195                 listLMs.addAll(newLMs);
196             }
197             return pos < listLMs.size();
198         }
199     }
200 
201     /** {@inheritDoc} */
202     @Override
createNextChildLMs(int pos)203     public boolean createNextChildLMs(int pos) {
204 
205         while (proxyLMiter.hasNext()) {
206             LayoutManager lm = proxyLMiter.next();
207             if (lm instanceof InlineLevelLayoutManager) {
208                 LineLayoutManager lineLM = createLineManager(lm);
209                 addChildLM(lineLM);
210             } else {
211                 addChildLM(lm);
212             }
213             if (pos < childLMs.size()) {
214                 return true;
215             }
216         }
217         return false;
218     }
219 
220     /**
221      * Create a new LineLM, and collect all consecutive
222      * inline generating LMs as its child LMs.
223      * @param firstlm First LM in new LineLM
224      * @return the newly created LineLM
225      */
createLineManager(LayoutManager firstlm)226     private LineLayoutManager createLineManager(LayoutManager firstlm) {
227         LineLayoutManager llm;
228         llm = new LineLayoutManager(getBlockFO(), lineHeight, lead, follow);
229         List<LayoutManager> inlines = new java.util.ArrayList<LayoutManager>();
230         inlines.add(firstlm);
231         while (proxyLMiter.hasNext()) {
232             LayoutManager lm = proxyLMiter.next();
233             if (lm instanceof InlineLevelLayoutManager) {
234                 inlines.add(lm);
235             } else {
236                 proxyLMiter.previous();
237                 break;
238             }
239         }
240         llm.addChildLMs(inlines);
241         return llm;
242     }
243 
244     /** {@inheritDoc} */
245     @Override
getKeepTogetherProperty()246     public KeepProperty getKeepTogetherProperty() {
247         return getBlockFO().getKeepTogether();
248     }
249 
250     /** {@inheritDoc} */
251     @Override
getKeepWithPreviousProperty()252     public KeepProperty getKeepWithPreviousProperty() {
253         return getBlockFO().getKeepWithPrevious();
254     }
255 
256     /** {@inheritDoc} */
257     @Override
getKeepWithNextProperty()258     public KeepProperty getKeepWithNextProperty() {
259         return getBlockFO().getKeepWithNext();
260     }
261 
262     /** {@inheritDoc} */
263     @Override
addAreas(PositionIterator parentIter, LayoutContext layoutContext)264     public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
265         getParentArea(null);
266 
267         // if this will create the first block area in a page
268         // and display-align is after or center, add space before
269         if (layoutContext.getSpaceBefore() > 0) {
270             addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore()));
271         }
272 
273         LayoutManager childLM;
274         LayoutManager lastLM = null;
275         LayoutContext lc = LayoutContext.offspringOf(layoutContext);
276         lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
277         // set space after in the LayoutContext for children
278         if (layoutContext.getSpaceAfter() > 0) {
279             lc.setSpaceAfter(layoutContext.getSpaceAfter());
280         }
281         PositionIterator childPosIter;
282 
283         // "unwrap" the NonLeafPositions stored in parentIter
284         // and put them in a new list;
285         LinkedList<Position> positionList = new LinkedList<Position>();
286         Position pos;
287         Position firstPos = null;
288         Position lastPos = null;
289         while (parentIter.hasNext()) {
290             pos = parentIter.next();
291             //log.trace("pos = " + pos.getClass().getName() + "; " + pos);
292             if (pos.getIndex() >= 0) {
293                 if (firstPos == null) {
294                     firstPos = pos;
295                 }
296                 lastPos = pos;
297             }
298             Position innerPosition = pos;
299             if (pos instanceof NonLeafPosition) {
300                 //Not all elements are wrapped
301                 innerPosition = pos.getPosition();
302             }
303 
304             if (innerPosition != null
305                     && (innerPosition.getLM() != this
306                         || innerPosition instanceof MappingPosition)) {
307                 // innerPosition was created by another LM
308                 positionList.add(innerPosition);
309                 lastLM = innerPosition.getLM();
310             }
311         }
312 
313         addId();
314 
315         registerMarkers(true, isFirst(firstPos), isLast(lastPos));
316 
317         // the Positions in positionList were inside the elements
318         // created by the LineLM
319         childPosIter = new PositionIterator(positionList.listIterator());
320 
321         while ((childLM = childPosIter.getNextChildLM()) != null) {
322             // set last area flag
323             lc.setFlags(LayoutContext.LAST_AREA,
324                     (layoutContext.isLastArea() && childLM == lastLM));
325             lc.setStackLimitBP(layoutContext.getStackLimitBP());
326             // Add the line areas to Area
327             childLM.addAreas(childPosIter, lc);
328         }
329 
330         registerMarkers(false, isFirst(firstPos), isLast(lastPos));
331 
332         TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(),
333                 effSpaceBefore, effSpaceAfter);
334         TraitSetter.setVisibility(curBlockArea, getBlockFO().getVisibility());
335         flush();
336 
337         curBlockArea = null;
338         resetSpaces();
339 
340         //Notify end of block layout manager to the PSLM
341         checkEndOfLayout(lastPos);
342     }
343 
344     /**
345      * Return an Area which can contain the passed childArea. The childArea
346      * may not yet have any content, but it has essential traits set.
347      * In general, if the LayoutManager already has an Area it simply returns
348      * it. Otherwise, it makes a new Area of the appropriate class.
349      * It gets a parent area for its area by calling its parent LM.
350      * Finally, based on the dimensions of the parent area, it initializes
351      * its own area. This includes setting the content IPD and the maximum
352      * BPD.
353      * @param childArea area to get the parent area for
354      * @return the parent area
355      */
356     @Override
getParentArea(Area childArea)357     public Area getParentArea(Area childArea) {
358         if (curBlockArea == null) {
359             curBlockArea = new Block();
360             curBlockArea.setChangeBarList(getChangeBarList());
361 
362             curBlockArea.setIPD(super.getContentAreaIPD());
363 
364             curBlockArea.setBidiLevel(getBlockFO().getBidiLevelRecursive());
365 
366             TraitSetter.addBreaks(curBlockArea,
367                     getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter());
368 
369             // Must get dimensions from parent area
370             //Don't optimize this line away. It can have ugly side-effects.
371             /*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea);
372 
373             // set traits
374             TraitSetter.setProducerID(curBlockArea, getBlockFO().getId());
375             TraitSetter.addBorders(curBlockArea,
376                     getBlockFO().getCommonBorderPaddingBackground(),
377                     discardBorderBefore, discardBorderAfter, false, false, this);
378             TraitSetter.addPadding(curBlockArea,
379                     getBlockFO().getCommonBorderPaddingBackground(),
380                     discardPaddingBefore, discardPaddingAfter, false, false, this);
381             TraitSetter.addMargins(curBlockArea,
382                     getBlockFO().getCommonBorderPaddingBackground(),
383                     startIndent, endIndent,
384                     this);
385             TraitSetter.setLayer(curBlockArea, getBlockFO().getLayer());
386 
387             curBlockArea.setLocale(getBlockFO().getCommonHyphenation().getLocale());
388             curBlockArea.setLocation(FONode.getLocatorString(getBlockFO().getLocator()));
389             setCurrentArea(curBlockArea); // ??? for generic operations
390         }
391         return curBlockArea;
392     }
393 
394     /** {@inheritDoc} */
395     @Override
addChildArea(Area childArea)396     public void addChildArea(Area childArea) {
397         if (curBlockArea != null) {
398             if (childArea instanceof LineArea) {
399                 curBlockArea.addLineArea((LineArea) childArea);
400             } else {
401                 curBlockArea.addBlock((Block) childArea);
402             }
403         }
404     }
405 
406     /**
407      * Force current area to be added to parent area.
408      * {@inheritDoc}
409      */
410     @Override
flush()411     protected void flush() {
412         if (curBlockArea != null) {
413             TraitSetter.addBackground(curBlockArea,
414                     getBlockFO().getCommonBorderPaddingBackground(),
415                     this);
416             super.flush();
417         }
418     }
419 
420     /**
421      * convenience method that returns the Block node
422      * @return the block node
423      */
getBlockFO()424     protected org.apache.fop.fo.flow.Block getBlockFO() {
425         return (org.apache.fop.fo.flow.Block) fobj;
426     }
427 
428     // --------- Property Resolution related functions --------- //
429 
430     /**
431      * Returns the IPD of the content area
432      * @return the IPD of the content area
433      */
434     @Override
getContentAreaIPD()435     public int getContentAreaIPD() {
436         if (curBlockArea != null) {
437             return curBlockArea.getIPD();
438         }
439         return super.getContentAreaIPD();
440     }
441 
442 
443     /**
444      * Returns the BPD of the content area
445      * @return the BPD of the content area
446      */
447     @Override
getContentAreaBPD()448     public int getContentAreaBPD() {
449         if (curBlockArea != null) {
450             return curBlockArea.getBPD();
451         }
452         return -1;
453     }
454 
455     /** {@inheritDoc} */
456     @Override
getGeneratesBlockArea()457     public boolean getGeneratesBlockArea() {
458         return true;
459     }
460 
461     /** {@inheritDoc} */
462     @Override
isRestartable()463     public boolean isRestartable() {
464         return true;
465     }
466 
467 }
468