Phase 2a: The <literal>getNextBreakPoss</literal> call tree
Overview Create a new layout context for the children. Then process the flow: loop until the flow is exhausted (isFinished()): Get the next possible breakpoint (getNextBreakPoss). Loop until the list of child layout managers is exhausted: Get a child layout manager (AbstractLayoutManager.getChildLM). The current child layout manager is returned until it is finished. Then the layout manager for the next child is returned. Create a new layout context for the children. If the child layout manager is not finished, get the next possible breakpoint (getNextBreakPoss). If a breakpoint is returned, break the loop and return the breakpoint. This finishes a page. Get the next possible breakpoint (getNextBreakPoss) (continued) Loop until the list of child layout managers is exhausted: (continued) Else if no breakpoint is returned, do the next cycle with the next child layout manager. Mark the layout manager as finished (the list of child layout managers is exhausted). At this point a complete (pseudo)tree of possible break points for a page has been collected.
How do layout managers get layout managers for the child FO nodes? Child layout managers are created and retrieved in the method AbstractLayoutManager.getChildLM. The layout manager gets the layout manager for its next child from its LMIter object childLMIter. This LMIter object contains an iterator over the children of the layout manager's FO node. It behaves itself as an iterator over the list of layout managers for the children. It constructs those layout managers when needed, in its preLoadNext method, which calls the convenience method preLoadList. Using the iterator fobjIter, preLoadList iterates through the children of the current FO-Object and preloads the corresponding LMs (LayoutManagerMaker.makeLayoutManagers()). Thus LMIter has a layout manager for its next child. It returns this layout manager in the call to its next() method, when the current layout manager invokes its getChildLM method. BlockLayoutManager.ProxyLMiter has its own subclass of LMIter, which implements a different method of creating child layout managers. See the next section. The method that creates the LMs, LayoutManagerMaker.makeLayoutManagers(), is implemented by FOP's implementation of the LayoutManagerMaker interface, LayoutManagerMapping. A different LM system can be hooked into FOP by inserting a different implementation of the LayoutManagerMaker interface. (See FOUserAgent.setLayoutManagerMakerOverride.) Also note that AbstractLayoutManager.getChildLM itself does not behave as an iterator. The current child layout manager is returned until it is finished. One can safely make multiple calls to getChildLM. If the current child layout manager is unfinished and does nothing in between the calls, it remains unfinished, and is returned at every call. If the current child layout manager is finished, the next layout manager is loaded, and, because it is unfinished, returned at every call. If this is the last child layout manager and it is finished, then null is returned because in LMiter.preLoadNext baseIter.hasNext() returns false. The latter case is used in BlockLayoutManager.getNextBreakPoss. Stack trace: Creating a new layout manager for a child, in LMiter.preLoadNext, in AbstractLayoutManager.getChildLM: [1] org.apache.fop.layoutmgr.LayoutManagerMapping.makeLayoutManagers(org.apache.fop.fo.FONode, java.util.List) line: 140 [2] org.apache.fop.layoutmgr.PageSequenceLayoutManager(org.apache.fop.layoutmgr.AbstractLayoutManager).preLoadList(int) line: 399 [3] org.apache.fop.layoutmgr.PageSequenceLayoutManager(org.apache.fop.layoutmgr.AbstractLayoutManager).preLoadNext(int) line: 409 [4] org.apache.fop.layoutmgr.LMiter.hasNext() line: 39 [5] org.apache.fop.layoutmgr.PageSequenceLayoutManager(org.apache.fop.layoutmgr.AbstractLayoutManager).getChildLM() line: 168 [6] org.apache.fop.layoutmgr.PageSequenceLayoutManager.getNextBreakPoss(org.apache.fop.layoutmgr.LayoutContext) line: 260 [7] org.apache.fop.layoutmgr.PageSequenceLayoutManager.activateLayout() line: 232
Block layout managers and their child layout managers Block LMs are different in their treatment of their child LMs. For this purpose BlockLayoutManager defines a nested class ProxyLMiter, which is a subclass of LMiter. This proxy is the basic LMiter over the children of the block. If the proxy produces a child LM that does not generate inline areas, the child LM is added to the list of child LMs as normal. But if the childLM generates an inline area, a new LineLayoutManager object is created (BlockLayoutManager.ProxyLMiter.createLineManager). This LM asks the proxy to produce more child LMs. As long as these child LMs generate inline areas, they are collected by the LineLayoutManager object. Finally, the LineLayoutManager object creates its LMiter object as the ListIterator over the list of collected child LMs.
About <literal>getNextBreakPoss</literal> and the list of child layout managers Note that the breakpoint may come from a deeply nested child. Each layout manager keeps a reference to its current child layout manager. The whole list is descended again (getChildLM) at the next call to getNextBreakPoss. TO BE IMPROVED Stack of layout managers: PageSequence PageLayoutManager Flow FlowLayoutManager Block BlockLayoutManager Block LineLayoutManager FOText TextLayoutManager For BlockLayoutManager and LineLayoutManager Block is the same, but their childLMIter are different: BlockLayoutManager$BlockLMiter vs AbstractList$ListItr [1] org.apache.fop.layoutmgr.TextLayoutManager.getNextBreakPoss (TextLayoutManager.java:270) [2] org.apache.fop.layoutmgr.LineLayoutManager.getNextBreakPoss (LineLayoutManager.java:212) [3] org.apache.fop.layoutmgr.BlockLayoutManager.getNextBreakPoss (BlockLayoutManager.java:229) [4] org.apache.fop.layoutmgr.FlowLayoutManager.getNextBreakPoss (FlowLayoutManager.java:111) [5] org.apache.fop.layoutmgr.PageLayoutManager.getNextBreakPoss (PageLayoutManager.java:261) [6] org.apache.fop.layoutmgr.PageLayoutManager.activateLayout (PageLayoutManager.java:228) A TextLayoutManager: this = { vecAreaInfo: instance of java.util.ArrayList(id=1062) chars: instance of char[13] (id=1064) textInfo: instance of org.apache.fop.fo.TextInfo(id=1065) iAreaStart: 0 iNextStart: 0 ipdTotal: null spaceCharIPD: 4448 hyphIPD: 5328 halfWS: instance of org.apache.fop.traits.SpaceVal(id=1066) iNbSpacesPending: 0 org.apache.fop.layoutmgr.AbstractLayoutManager.userAgent: instance of org.apache.fop.apps.FOUserAgent(id=1067) org.apache.fop.layoutmgr.AbstractLayoutManager.parentLM: instance of org.apache.fop.layoutmgr.LineLayoutManager(id=1068) org.apache.fop.layoutmgr.AbstractLayoutManager.fobj: instance of org.apache.fop.fo.FOText(id=1069) org.apache.fop.layoutmgr.AbstractLayoutManager.foID: null org.apache.fop.layoutmgr.AbstractLayoutManager.markers: null org.apache.fop.layoutmgr.AbstractLayoutManager.bFinished: false org.apache.fop.layoutmgr.AbstractLayoutManager.curChildLM: null org.apache.fop.layoutmgr.AbstractLayoutManager.childLMiter: instance of org.apache.fop.layoutmgr.LMiter(id=1070) org.apache.fop.layoutmgr.AbstractLayoutManager.bInited: true } Text in fo:text is handled by a TextLayoutManager. Two routines add the text and calculate the next possible break.
<literal>LineLayoutManager.getNextBreakPoss</literal>
Prepare for the main loop Create a new empty list of possible line endings, vecPossEnd. Retrieve the ipd availIPD from the layout context. Create a new layout context (inline layout context) for the child layout managers, based on the layout context for this layout manager. Clear the map of previous ipds. Record the length of vecInlineBreaks, which we can use to find the last breakposs of the previous line. Set prevBP to null; prevBP contains the last confirmed breakposs of this line.
The main loop over the list of child layout managers Loop until the list of child layout managers is exhausted: Get a child layout manager (AbstractLayoutManager.getChildLM). The current child layout manager is returned until it is finished. Then the layout manager for the next child is returned. Record the last breakposs. Record whether the breakposs we are going to find is the first breakposs of this line. Record whether it is the first breakposs of this child layout manager. Initialize the inline layout context (note that it is not a new layout context, the same inline layout context is used by all child layout managers) (method InlineStackingLayout.initChildLC): Record whether this is a new area; it is a new area if this is the start of a new line or of a new child LM. If this is the start of a new line record whether this is the first area of this child LM, set the leading space as passed by argument. Else if this starts a new child LM record that this is the first area, set the leading space from the previous BP. Else set the leading space to null. Record on the inline layout context whether leading space is supppressed; it is suppressed if this is the start of a new line, but not the start of this child LM, and the previous line was not ended by a forced break. Retrieve the next breakposs from the current child LM (getNextBreakPoss method of child LM). If it is not null: Calculate the ipd up to the previous BP (method InlineStackingLayout.updatePrevIPD): Take an empty ipd size. If this starts a new line: if it has a leading fence, add leading space (?), list the ipd for the LM of this BP in the map of previous ipds. Else retrieve the ipd for the LM of this BP in the map of previous ipds, if that is null (first BP of this child LM) retrieve the ipd for the LM of the previous BP in the map of previous ipds, add the leading space of this BP, add the pending space-end (stacking size) of the previous BP, list the ipd for the LM of this BP in the map of previous ipds. Add to the ipd the pending space-end (stacking size) of this BP. Record whether this BP could end the line: if a break may occur after this BP, record true; else if this BP is suppressible at a line break, return false; else, return whether this is the last child LM and it is finished, or the next area could start a new line. If this BP could end the line, add trailing space. If this BP exceeds the line length (bpDim.min > availIPD.max), if the text should be justified or if this is the first BP of this line, if we are in a hyphenation try, break the loop; we have exhausted our options and one of the previous BPs should end the line (_exit of loop_); if this BP could not end the line, add it to the list of inline breaks, and continue with the next iteration; prepare to hyphenate: get the hyphenation context for the text between the last and this BP (method getHyphenContext): add this BP to the list of inline breaks; even though this is not a good BP, we add it to the list, so that we can retrieve the text between the last and this BP; iterate back to the previous BP in this list; loop over the following BPs in this list: retrieve the text between the preceding and this BP. remove this BP again from the list of inline breaks. create a hyphenation object for the retrieved text, taking the language, country and other hyphenation properties into account. create a hyphenation context object from it, and return that. store the hyphenation context with the inline layout context. Record on the inline layout context that we are in a hyphenation try. reset the child LMs to the previous BP or to the start of the line. Else (if text should not be justified and if this is not the first BP of this line) break the loop; one of the previous BPs should end the line (_exit of loop_); Else (if this BP does not exceed the line length): add this BP to the list of inline breaks, if this BP could end the line, record it as the last confirmed BP: set prevBP to this BP. if this BP is a forced line break, break the loop; this BP (or one of the previous BPs?) should end the line (_exit of loop_). if this BP may fill the line length (bpDim.max >= availIPD.min), add it to the list of possible line endings, vecPossEnd, with a cost which is equal to the difference of the optimal values of the content length and the line length (Math.abs(availIPD.opt - bpDim.opt)). If we are in a hypenation try, and the hyphenation context has no more hyphenation points, break the loop; this or one of the previous BPs should end the line (_exit of loop_).
After the main loop There are five exit points of the main loop: The last BP in the hyphenation try has exceeded the line length. The last BP has exceeded the line length, and we cannot get a hyphenation context. The last BP has exceeded the line length, and we do not hyphenate. The last BP has not exceeded the line length but forces a line break. We have run out of hyphenation points (and the last BP has not exceeded the line length). Natural end of the while loop: we are through the list of child layout managers. If the last BP has exceeded the line length, it is not in the list of inline breaks, and prevBP points to the last good break; otherwise it is in the list of inline breaks, and prevBP points to it. If we are through the list of child LMs, mark this LM as finished. If no BP was produced, return null. (This should concur with being finished?) If prevBP is null, there is not last good break; set it to this BP, even though it fails some criteria: it has exceeded the line length in the hyphenation try or we cannot get a hyphenation context, or it cannot end the line but it is the last BP of the last child layout manager. If this BP is not a forced break, and there are several possible line breaks, select the best one; make prevBP point to it. (Could this produce the wrong result if the BP has exceeded the line length and at the same time is a forced line break? Should prevBP be tested for being a forced break instead?) If the last BP is not the actual breakpoint prevBP (bp != prevBP) and the material after prevBP is not suppressible at the end of a line, back up to prevBP for a proper start of the next line. If the text should be justified and the breakpoint is a forced line break (here prevBP is tested) or this is the last line of the layout manager, set text alignment to left-aligned. Make a line break and return the associated breakposs.
<literal>LineLayoutManager.makeLineBreak</literal> Arguments are: index in vecInlineBreaks of line breaking BP of last line, target line length, type of text alignment. Calculate line dimensions. The Line LayoutManager contains the parameters lineHeight, lead and follow as members, which it received from its Block LayoutManager parent at construction. The Blo ckLayoutManager contains these parameters as members as well, and has received them from a TextInfo object in the method set BlockTextInfo. The TextInfo object has a reference to the Font object. lead is the font ascender, follow is the font descender. The leading is the difference between lineHeight and font height = ascender + descender. The leading is split in two halves, one for above, the other for below the line. The variable lineLead is calculated as the distance from the baseline to the top of the line, the variable maxtb is calculated as the distance from the baseline to the bottom of the line. The variable middlefollow set equal to maxtb. These parameters correspond to the members lead, total and follow of the breakposs. Find out the exact meaning of these. Loop over the breakposs in vecInlineBreaks: adjust lineLead, maxtb and middlefollow if the corresponding dimension of the BP is larger; add the ipds; a BP does not just hold the ipd of the area since the previous BP, but the cumulative ipd for all the areas contributed by its layout manager; therefore care is taken to add only the ipd of the last BP of each LM; more precisely, the ipd of the last BP of the previous LM is added when the next LM is encountered; Add the ipd of the last BP. Resolve the trailing space of the last BP. Adjust middlefollow if it is smaller than maxtb - lineHeight. Calculate the stretch or shrink factor ipdAdjust for the stretch- and shrinkable elements in this line: if content optimum is larger than line length optimum, if content minimum is smaller than line length optimum, calculate ipdAdjust between 0 and -1, real line length = line length optimum, else (content minimum is larger than line length optimum) ipdAdjust = -1, real line length = content minimum, else (content optimum is smaller than line length optimum), if content maximum is larger than line length optimum, calculate ipdAdjust between 0 and 1, real line length = line length optimum, else (content maximum is smaller than line length optimum), ipdAdjust = 1, real line length = content maximum. Calculate the stretch or shrink factor dAdjust for white space and the indent: justify: calculate dAdjust, center or end: calculate the required indent. Create a new LineBreakPosition based on this LM, the last BP in vecInlineBreaks and the calculated dimensions; use lineLead for the baseline position and lineLead + middlefollow for the lineHeight. Create a BreakPoss from this LineBreakPosition object. Mark the BP as last if this LM is finished. Set the stacking size of the BP (bpd) equal to lineLead + middlefollow.
Line LayoutManager, a sequence of breakposs The text is: "waterstaatsingenieur ministersportefeuille aandachtstrekker. Vernederlandste vakliteratuur" etc. It consists of a few long Dutch words, which can be found in the hyphenation exceptions in nl.xml. The column width is 8cm. This text and width have been chosen so that they force many hyphenations. This text is contained in a single FOText node, and is dealt with by a single Text LayoutManager, which is a child layout manager for the Line LayoutManager. The Text LayoutManager maintains a list of area information objects, vecAreaInfo, and the Line LayoutManager maintains a list of breakposs, vecInlineBreaks. Each breakposs refers to an area, in its position.iLeafPos member. During the process of finding suitable line breaks, breakposs are generated until one is found that exceeds the line length. Then the Line LayoutManager backs up to the previous BP, either to start a hyphenation try or to create the line break. When it creates a line break, the last breakposs, which exceeds the line length, is not added to the list of inline breaks. When it starts a hyphenation try, the breakposs is added to that list, and removed again after the text for hyphenation has been obtained. Each time, the corresponding area info is left in the list of area information objects. As a consequence the list vecAreaInfo is longer than the list vecInlineBreaks, and some areas have no corresponding breakposs. wa-ter-staats-in-ge-nieur mi-nis-ters-por-te-feuil-le aandachtstrekker. 0 2 5 1 1 1 2 2 2 2 3 3 3 4 4 6 1 3 5 0 3 6 9 0 3 5 0 2 0 text: waterstaatsingenieur wa ter staats in | AreaInfo: 0: 0-20 (removed) 0: 0-2 1: 2-5 2: 5-11 3: 11-13 | InlineBreaks: 0 (removed) 0 1 2 3 | too long, hyphenate, | back up V gen genieur _ministersportefeuille _mi nis | 4: 13-15 5: 13-20 6: 20-42 7: 20-23 8: 23-26 | 4 5 (removed) 5 6 | too long, too long, hyphenate | line break, back up | back up v ters tersportefeuille_ ter s por 9: 26-30 10: 26-42 11: 26-29 12: 29-30 13: 30-33 7 (removed) 7 8 9 too long, too long, hyphenate, hyphenation line break, back up error back up te feuil | le | _aandachtstrekker. 14: 33-35 15: 35-40 | 16: 40-42 | 17: 42-60 10 11 | 12 | 13 (removed) last hyphenpoint, | | too long, line break v | hyphenation fails, | line break, v back up aandachtstrekker. | etc. 18: 43-60 | 13 | too long, | hyphenation fails, | first BP, | line break v A few remarkable points: The first AreaInfo object is removed from the list during backing up. This is because the previous BP, which is null, does not belong to this child layout manager, and therefore the whole child LM is reset. The last BP, no. 18, exceeds the line length, but because it is the first BP of this line, it is accepted for the line break. BP 7 at position 29 falls at a point that is not a good hyphenation point. This is probably caused by the fact that for this line only the partial word is subjected to hyphenation. On the previous line the complete word was subjected to hyphenation, and there was no BP at position 29. For the word "aandachtstrekker." hyphenation fails (hyph == null is returned). This may be due to the period not being separated from the word. When the period is removed, the word is properly broken.
<literal>TextLayoutManager.getNextBreakPoss</literal> If this is the first call to this Text LayoutManager, initialize ipdTotal and record that this breakposs is the first in iFlags. If leading spaces must be suppressed, suppress all leading space characters U+20. Return if this finished the text. For the remaining leading space characters, If this is a space U+20 or a non-breaking space U+A0, count it; if this is the first character and this is the first breakposs, if the context has leading spaces, add it (or halfWS?); else add it (or halfWS?) to the pending space, and add the pending space to the space ipd. add the space width to the word ipd. set the pending space to halfWS. if this is a non-breaking space U+A0, register it in bSawNonSuppressible. Else (other space characters), register it in bSawNonSuppressible. add the pending space to the space ipd, and clear the pending space. add the character width to the word ipd. If this finishes the text, register whether there were any nonsuppressible spaces (bSawNonSuppressible) in iFlags. pass all the info to makeBreakPoss and _return_ its breakposs. Else, add pending space. If hypenation is on, get the size of the next syllable: get the size of the next syllable, if successful, add the flags BreakPoss.CAN_BREAK_AFTER and BreakPoss.CAN_BREAK_AFTER in iFlags, add the syllable length to the word ipd. Else look for a legal line-break: breakable white-space and certain characters such as '-' which can serve as word breaks; don't look for hyphenation points here though, for all following characters: If this is a newline character U+0A, or if textInfo.bWrap and this is a breakable space, or if this is one of the linebreak characters ("-/") and it is the first character or the preceding character is a letter or a digit, add the flag BreakPoss.CAN_BREAK_AFTER to iFlags, if this is not a space U+20, move the counter to the next character, if this is a newline U+0A, add the flag BreakPoss.FORCE to iFlags, else add the character width to the word ipd. if the rest of the text consists of spaces U+20, register that the rest is suppressible at a line break (BreakPoss.REST_ARE_SUPPRESS_AT_LB) in iFlags, pass all the info to makeBreakPoss and _return_ its breakposs. Else add the character width to the word ipd, and continue with the cycle for the next character. At the end of the text, pass all the info to makeBreakPoss and _return_ its breakposs.
<literal>TextLayoutManager.makeBreakPoss</literal> Make word ipd into a MinOptMax object. Add space ipd to it. Add total ipd from previous texts to it. Create an AreaInfo object for this text fragment and add it to the vector of AreaInfo objects. Create a breakposs for this text fragment. Set the total ipd to the current ipd. If the flags contain BreakPoss.HYPHENATED, set the stacking size to the ipd plus the width of the hyphen character, Else set the stacking size to the ipd. Set the non-stacking size to the line height, from the text info. Register the descender and ascender with the breakposs object; this is currently commented out. If this is the end of the text, add BreakPoss.ISLAST to the flags, declare the LM finished. Register the flags with the breakposs object Register the pending space or the absence thereof with the breakposs object. Register the leading space or the absence thereof with the breakposs object. Return the breakposs object.