001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: BlockLayoutManager.java 453310 2006-10-05 18:44:15Z spepping $ */
019:
020: package org.apache.fop.layoutmgr;
021:
022: import java.util.LinkedList;
023: import java.util.List;
024: import java.util.ListIterator;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.fop.area.Area;
029: import org.apache.fop.area.Block;
030: import org.apache.fop.area.LineArea;
031: import org.apache.fop.datatypes.Length;
032: import org.apache.fop.fonts.Font;
033: import org.apache.fop.layoutmgr.inline.InlineLayoutManager;
034: import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager;
035: import org.apache.fop.layoutmgr.inline.LineLayoutManager;
036: import org.apache.fop.traits.MinOptMax;
037: import org.apache.fop.traits.SpaceVal;
038:
039: /**
040: * LayoutManager for a block FO.
041: */
042: public class BlockLayoutManager extends BlockStackingLayoutManager
043: implements ConditionalElementListener {
044:
045: /**
046: * logging instance
047: */
048: private static Log log = LogFactory
049: .getLog(BlockLayoutManager.class);
050:
051: private Block curBlockArea;
052:
053: /** Iterator over the child layout managers. */
054: protected ListIterator proxyLMiter;
055:
056: private int lead = 12000;
057: private Length lineHeight;
058: private int follow = 2000;
059: private int middleShift = 0;
060:
061: private boolean discardBorderBefore;
062: private boolean discardBorderAfter;
063: private boolean discardPaddingBefore;
064: private boolean discardPaddingAfter;
065: private MinOptMax effSpaceBefore;
066: private MinOptMax effSpaceAfter;
067:
068: /** The list of child BreakPoss instances. */
069: protected List childBreaks = new java.util.ArrayList();
070:
071: /**
072: * Creates a new BlockLayoutManager.
073: * @param inBlock the block FO object to create the layout manager for.
074: */
075: public BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock) {
076: super (inBlock);
077: proxyLMiter = new ProxyLMiter();
078: }
079:
080: public void initialize() {
081: super .initialize();
082: Font fs = getBlockFO().getCommonFont().getFontState(
083: getBlockFO().getFOEventHandler().getFontInfo(), this );
084:
085: lead = fs.getAscender();
086: follow = -fs.getDescender();
087: middleShift = -fs.getXHeight() / 2;
088: lineHeight = getBlockFO().getLineHeight().getOptimum(this )
089: .getLength();
090: startIndent = getBlockFO().getCommonMarginBlock().startIndent
091: .getValue(this );
092: endIndent = getBlockFO().getCommonMarginBlock().endIndent
093: .getValue(this );
094: foSpaceBefore = new SpaceVal(getBlockFO()
095: .getCommonMarginBlock().spaceBefore, this ).getSpace();
096: foSpaceAfter = new SpaceVal(
097: getBlockFO().getCommonMarginBlock().spaceAfter, this )
098: .getSpace();
099: bpUnit = 0; // non-standard extension
100: if (bpUnit == 0) {
101: // use optimum space values
102: adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore
103: .getSpace().getOptimum(this ).getLength().getValue(
104: this );
105: adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter
106: .getSpace().getOptimum(this ).getLength().getValue(
107: this );
108: } else {
109: // use minimum space values
110: adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore
111: .getSpace().getMinimum(this ).getLength().getValue(
112: this );
113: adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter
114: .getSpace().getMinimum(this ).getLength().getValue(
115: this );
116: }
117: }
118:
119: /** @see org.apache.fop.layoutmgr.BlockStackingLayoutManager */
120: public LinkedList getNextKnuthElements(LayoutContext context,
121: int alignment) {
122: resetSpaces();
123: return super .getNextKnuthElements(context, alignment);
124: }
125:
126: private void resetSpaces() {
127: this .discardBorderBefore = false;
128: this .discardBorderAfter = false;
129: this .discardPaddingBefore = false;
130: this .discardPaddingAfter = false;
131: this .effSpaceBefore = null;
132: this .effSpaceAfter = null;
133: }
134:
135: /**
136: * Proxy iterator for Block LM.
137: * This iterator creates and holds the complete list
138: * of child LMs.
139: * It uses fobjIter as its base iterator.
140: * Block LM's createNextChildLMs uses this iterator
141: * as its base iterator.
142: */
143: protected class ProxyLMiter extends LMiter {
144:
145: /*
146: * Constructs a proxy iterator for Block LM.
147: */
148: public ProxyLMiter() {
149: super (BlockLayoutManager.this );
150: listLMs = new java.util.ArrayList(10);
151: }
152:
153: /**
154: * @return true if there are more child lms
155: */
156: public boolean hasNext() {
157: return (curPos < listLMs.size()) ? true
158: : createNextChildLMs(curPos);
159: }
160:
161: /**
162: * @return true if new child lms were added
163: */
164: protected boolean createNextChildLMs(int pos) {
165: List newLMs = createChildLMs(pos + 1 - listLMs.size());
166: if (newLMs != null) {
167: listLMs.addAll(newLMs);
168: }
169: return pos < listLMs.size();
170: }
171: }
172:
173: /**
174: * @see org.apache.fop.layoutmgr.LayoutManager#createNextChildLMs
175: */
176: public boolean createNextChildLMs(int pos) {
177:
178: while (proxyLMiter.hasNext()) {
179: LayoutManager lm = (LayoutManager) proxyLMiter.next();
180: if (lm instanceof InlineLevelLayoutManager) {
181: LineLayoutManager lineLM = createLineManager(lm);
182: addChildLM(lineLM);
183: } else {
184: addChildLM(lm);
185: }
186: if (pos < childLMs.size()) {
187: return true;
188: }
189: }
190: return false;
191: }
192:
193: /**
194: * Create a new LineLM, and collect all consecutive
195: * inline generating LMs as its child LMs.
196: * @param firstlm First LM in new LineLM
197: * @return the newly created LineLM
198: */
199: private LineLayoutManager createLineManager(LayoutManager firstlm) {
200: LineLayoutManager llm;
201: llm = new LineLayoutManager(getBlockFO(), lineHeight, lead,
202: follow);
203: List inlines = new java.util.ArrayList();
204: inlines.add(firstlm);
205: while (proxyLMiter.hasNext()) {
206: LayoutManager lm = (LayoutManager) proxyLMiter.next();
207: if (lm instanceof InlineLevelLayoutManager) {
208: inlines.add(lm);
209: } else {
210: proxyLMiter.previous();
211: break;
212: }
213: }
214: llm.addChildLMs(inlines);
215: return llm;
216: }
217:
218: /**
219: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether()
220: */
221: public boolean mustKeepTogether() {
222: // TODO Keeps will have to be more sophisticated sooner or later
223: // TODO This is a quick fix for the fact that the parent is not always a BlockLevelLM;
224: // eventually mustKeepTogether() must be moved up to the LM interface
225: return (!getBlockFO().getKeepTogether().getWithinPage()
226: .isAuto()
227: || !getBlockFO().getKeepTogether().getWithinColumn()
228: .isAuto()
229: || (getParent() instanceof BlockLevelLayoutManager && ((BlockLevelLayoutManager) getParent())
230: .mustKeepTogether()) || (getParent() instanceof InlineLayoutManager && ((InlineLayoutManager) getParent())
231: .mustKeepTogether()));
232: }
233:
234: /**
235: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious()
236: */
237: public boolean mustKeepWithPrevious() {
238: return !getBlockFO().getKeepWithPrevious().getWithinPage()
239: .isAuto()
240: || !getBlockFO().getKeepWithPrevious()
241: .getWithinColumn().isAuto();
242: }
243:
244: /**
245: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext()
246: */
247: public boolean mustKeepWithNext() {
248: return !getBlockFO().getKeepWithNext().getWithinPage().isAuto()
249: || !getBlockFO().getKeepWithNext().getWithinColumn()
250: .isAuto();
251: }
252:
253: /**
254: * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(org.apache.fop.layoutmgr.PositionIterator, org.apache.fop.layoutmgr.LayoutContext)
255: */
256: public void addAreas(PositionIterator parentIter,
257: LayoutContext layoutContext) {
258: getParentArea(null);
259:
260: // if this will create the first block area in a page
261: // and display-align is after or center, add space before
262: if (layoutContext.getSpaceBefore() > 0) {
263: addBlockSpacing(0.0, new MinOptMax(layoutContext
264: .getSpaceBefore()));
265: }
266:
267: LayoutManager childLM = null;
268: LayoutManager lastLM = null;
269: LayoutContext lc = new LayoutContext(0);
270: lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
271: // set space after in the LayoutContext for children
272: if (layoutContext.getSpaceAfter() > 0) {
273: lc.setSpaceAfter(layoutContext.getSpaceAfter());
274: }
275: PositionIterator childPosIter;
276:
277: // "unwrap" the NonLeafPositions stored in parentIter
278: // and put them in a new list;
279: LinkedList positionList = new LinkedList();
280: Position pos;
281: boolean bSpaceBefore = false;
282: boolean bSpaceAfter = false;
283: Position firstPos = null;
284: Position lastPos = null;
285: while (parentIter.hasNext()) {
286: pos = (Position) parentIter.next();
287: //log.trace("pos = " + pos.getClass().getName() + "; " + pos);
288: if (pos.getIndex() >= 0) {
289: if (firstPos == null) {
290: firstPos = pos;
291: }
292: lastPos = pos;
293: }
294: Position innerPosition = pos;
295: if (pos instanceof NonLeafPosition) {
296: //Not all elements are wrapped
297: innerPosition = ((NonLeafPosition) pos).getPosition();
298: }
299: if (innerPosition == null) {
300: // pos was created by this BlockLM and was inside an element
301: // representing space before or after
302: // this means the space was not discarded
303: if (positionList.size() == 0) {
304: // pos was in the element representing space-before
305: bSpaceBefore = true;
306: //log.trace(" space before");
307: } else {
308: // pos was in the element representing space-after
309: bSpaceAfter = true;
310: //log.trace(" space-after");
311: }
312: } else if (innerPosition.getLM() == this
313: && !(innerPosition instanceof MappingPosition)) {
314: // pos was created by this BlockLM and was inside a penalty
315: // allowing or forbidding a page break
316: // nothing to do
317: //log.trace(" penalty");
318: } else {
319: // innerPosition was created by another LM
320: positionList.add(innerPosition);
321: lastLM = innerPosition.getLM();
322: //log.trace(" " + innerPosition.getClass().getName());
323: }
324: }
325:
326: getPSLM().addIDToPage(getBlockFO().getId());
327: if (markers != null) {
328: getCurrentPV().addMarkers(markers, true, isFirst(firstPos),
329: isLast(lastPos));
330: }
331:
332: if (bpUnit == 0) {
333: // the Positions in positionList were inside the elements
334: // created by the LineLM
335: childPosIter = new StackingIter(positionList.listIterator());
336: } else {
337: // the Positions in positionList were inside the elements
338: // created by the BlockLM in the createUnitElements() method
339: //if (((Position) positionList.getLast()) instanceof
340: // LeafPosition) {
341: // // the last item inside positionList is a LeafPosition
342: // // (a LineBreakPosition, more precisely); this means that
343: // // the whole paragraph is on the same page
344: // childPosIter = new KnuthPossPosIter(storedList, 0,
345: // storedList.size());
346: //} else {
347: // // the last item inside positionList is a Position;
348: // // this means that the paragraph has been split
349: // // between consecutive pages
350: LinkedList splitList = new LinkedList();
351: int splitLength = 0;
352: int iFirst = ((MappingPosition) positionList.getFirst())
353: .getFirstIndex();
354: int iLast = ((MappingPosition) positionList.getLast())
355: .getLastIndex();
356: // copy from storedList to splitList all the elements from
357: // iFirst to iLast
358: ListIterator storedListIterator = storedList
359: .listIterator(iFirst);
360: while (storedListIterator.nextIndex() <= iLast) {
361: KnuthElement element = (KnuthElement) storedListIterator
362: .next();
363: // some elements in storedList (i.e. penalty items) were created
364: // by this BlockLM, and must be ignored
365: if (element.getLayoutManager() != this ) {
366: splitList.add(element);
367: splitLength += element.getW();
368: lastLM = element.getLayoutManager();
369: }
370: }
371: //log.debug("Adding areas from " + iFirst + " to " + iLast);
372: //log.debug("splitLength= " + splitLength
373: // + " (" + neededUnits(splitLength) + " units') "
374: // + (neededUnits(splitLength) * bpUnit - splitLength)
375: // + " spacing");
376: // add space before and / or after the paragraph
377: // to reach a multiple of bpUnit
378: if (bSpaceBefore && bSpaceAfter) {
379: foSpaceBefore = new SpaceVal(getBlockFO()
380: .getCommonMarginBlock().spaceBefore, this )
381: .getSpace();
382: foSpaceAfter = new SpaceVal(getBlockFO()
383: .getCommonMarginBlock().spaceAfter, this )
384: .getSpace();
385: adjustedSpaceBefore = (neededUnits(splitLength
386: + foSpaceBefore.min + foSpaceAfter.min)
387: * bpUnit - splitLength) / 2;
388: adjustedSpaceAfter = neededUnits(splitLength
389: + foSpaceBefore.min + foSpaceAfter.min)
390: * bpUnit - splitLength - adjustedSpaceBefore;
391: } else if (bSpaceBefore) {
392: adjustedSpaceBefore = neededUnits(splitLength
393: + foSpaceBefore.min)
394: * bpUnit - splitLength;
395: } else {
396: adjustedSpaceAfter = neededUnits(splitLength
397: + foSpaceAfter.min)
398: * bpUnit - splitLength;
399: }
400: //log.debug("spazio prima = " + adjustedSpaceBefore
401: // + " spazio dopo = " + adjustedSpaceAfter + " totale = " +
402: // (adjustedSpaceBefore + adjustedSpaceAfter + splitLength));
403: childPosIter = new KnuthPossPosIter(splitList, 0, splitList
404: .size());
405: //}
406: }
407:
408: while ((childLM = childPosIter.getNextChildLM()) != null) {
409: // set last area flag
410: lc.setFlags(LayoutContext.LAST_AREA, (layoutContext
411: .isLastArea() && childLM == lastLM));
412: lc.setStackLimit(layoutContext.getStackLimit());
413: // Add the line areas to Area
414: childLM.addAreas(childPosIter, lc);
415: }
416:
417: if (markers != null) {
418: getCurrentPV().addMarkers(markers, false,
419: isFirst(firstPos), isLast(lastPos));
420: }
421:
422: TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext
423: .getSpaceAdjust(), effSpaceBefore, effSpaceAfter);
424: flush();
425:
426: curBlockArea = null;
427: resetSpaces();
428:
429: // Notify end of block layout manager to the PSLM
430: getPSLM().notifyEndOfLayout(getBlockFO().getId());
431: }
432:
433: /**
434: * Return an Area which can contain the passed childArea. The childArea
435: * may not yet have any content, but it has essential traits set.
436: * In general, if the LayoutManager already has an Area it simply returns
437: * it. Otherwise, it makes a new Area of the appropriate class.
438: * It gets a parent area for its area by calling its parent LM.
439: * Finally, based on the dimensions of the parent area, it initializes
440: * its own area. This includes setting the content IPD and the maximum
441: * BPD.
442: * @param childArea area to get the parent area for
443: * @return the parent area
444: */
445: public Area getParentArea(Area childArea) {
446: if (curBlockArea == null) {
447: curBlockArea = new Block();
448:
449: curBlockArea.setIPD(super .getContentAreaIPD());
450:
451: TraitSetter.addBreaks(curBlockArea, getBlockFO()
452: .getBreakBefore(), getBlockFO().getBreakAfter());
453:
454: // Must get dimensions from parent area
455: //Don't optimize this line away. It can have ugly side-effects.
456: /*Area parentArea =*/parentLM.getParentArea(curBlockArea);
457:
458: // set traits
459: TraitSetter.setProducerID(curBlockArea, getBlockFO()
460: .getId());
461: TraitSetter.addBorders(curBlockArea, getBlockFO()
462: .getCommonBorderPaddingBackground(),
463: discardBorderBefore, discardBorderAfter, false,
464: false, this );
465: TraitSetter.addPadding(curBlockArea, getBlockFO()
466: .getCommonBorderPaddingBackground(),
467: discardPaddingBefore, discardPaddingAfter, false,
468: false, this );
469: TraitSetter.addMargins(curBlockArea, getBlockFO()
470: .getCommonBorderPaddingBackground(), startIndent,
471: endIndent, this );
472:
473: setCurrentArea(curBlockArea); // ??? for generic operations
474: }
475: return curBlockArea;
476: }
477:
478: /**
479: * @see org.apache.fop.layoutmgr.LayoutManager#addChildArea(Area)
480: */
481: public void addChildArea(Area childArea) {
482: if (curBlockArea != null) {
483: if (childArea instanceof LineArea) {
484: curBlockArea.addLineArea((LineArea) childArea);
485: } else {
486: curBlockArea.addBlock((Block) childArea);
487: }
488: }
489: }
490:
491: /**
492: * Force current area to be added to parent area.
493: * @see org.apache.fop.layoutmgr.BlockStackingLayoutManager#flush()
494: */
495: protected void flush() {
496: if (curBlockArea != null) {
497: TraitSetter.addBackground(curBlockArea, getBlockFO()
498: .getCommonBorderPaddingBackground(), this );
499: super .flush();
500: }
501: }
502:
503: /**
504: * @see org.apache.fop.layoutmgr.LayoutManager#resetPosition(org.apache.fop.layoutmgr.Position)
505: */
506: public void resetPosition(Position resetPos) {
507: if (resetPos == null) {
508: reset(null);
509: childBreaks.clear();
510: } else {
511: //reset(resetPos);
512: LayoutManager lm = resetPos.getLM();
513: }
514: }
515:
516: /**
517: * convenience method that returns the Block node
518: * @return the block node
519: */
520: protected org.apache.fop.fo.flow.Block getBlockFO() {
521: return (org.apache.fop.fo.flow.Block) fobj;
522: }
523:
524: // --------- Property Resolution related functions --------- //
525:
526: /**
527: * Returns the IPD of the content area
528: * @return the IPD of the content area
529: */
530: public int getContentAreaIPD() {
531: if (curBlockArea != null) {
532: return curBlockArea.getIPD();
533: }
534: return super .getContentAreaIPD();
535: }
536:
537: /**
538: * Returns the BPD of the content area
539: * @return the BPD of the content area
540: */
541: public int getContentAreaBPD() {
542: if (curBlockArea != null) {
543: return curBlockArea.getBPD();
544: }
545: return -1;
546: }
547:
548: /**
549: * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesBlockArea
550: */
551: public boolean getGeneratesBlockArea() {
552: return true;
553: }
554:
555: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
556: public void notifySpace(RelSide side, MinOptMax effectiveLength) {
557: if (RelSide.BEFORE == side) {
558: if (log.isDebugEnabled()) {
559: log
560: .debug(this + ": Space " + side + ", "
561: + this .effSpaceBefore + "-> "
562: + effectiveLength);
563: }
564: this .effSpaceBefore = effectiveLength;
565: } else {
566: if (log.isDebugEnabled()) {
567: log.debug(this + ": Space " + side + ", "
568: + this .effSpaceAfter + "-> " + effectiveLength);
569: }
570: this .effSpaceAfter = effectiveLength;
571: }
572: }
573:
574: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
575: public void notifyBorder(RelSide side, MinOptMax effectiveLength) {
576: if (effectiveLength == null) {
577: if (RelSide.BEFORE == side) {
578: this .discardBorderBefore = true;
579: } else {
580: this .discardBorderAfter = true;
581: }
582: }
583: if (log.isDebugEnabled()) {
584: log.debug(this + ": Border " + side + " -> "
585: + effectiveLength);
586: }
587: }
588:
589: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
590: public void notifyPadding(RelSide side, MinOptMax effectiveLength) {
591: if (effectiveLength == null) {
592: if (RelSide.BEFORE == side) {
593: this .discardPaddingBefore = true;
594: } else {
595: this .discardPaddingAfter = true;
596: }
597: }
598: if (log.isDebugEnabled()) {
599: log.debug(this + ": Padding " + side + " -> "
600: + effectiveLength);
601: }
602: }
603:
604: }
|