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: TableLayoutManager.java 533789 2007-04-30 16:30:49Z vhennebert $ */
019:
020: package org.apache.fop.layoutmgr.table;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.apache.fop.fo.flow.Table;
025: import org.apache.fop.fo.flow.TableColumn;
026: import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
027: import org.apache.fop.layoutmgr.ConditionalElementListener;
028: import org.apache.fop.layoutmgr.KnuthElement;
029: import org.apache.fop.layoutmgr.KnuthGlue;
030: import org.apache.fop.layoutmgr.LayoutContext;
031: import org.apache.fop.layoutmgr.ListElement;
032: import org.apache.fop.layoutmgr.NonLeafPosition;
033: import org.apache.fop.layoutmgr.PositionIterator;
034: import org.apache.fop.layoutmgr.Position;
035: import org.apache.fop.layoutmgr.RelSide;
036: import org.apache.fop.layoutmgr.TraitSetter;
037: import org.apache.fop.area.Area;
038: import org.apache.fop.area.Block;
039: import org.apache.fop.traits.MinOptMax;
040: import org.apache.fop.traits.SpaceVal;
041:
042: import java.util.Iterator;
043: import java.util.LinkedList;
044: import org.apache.fop.datatypes.LengthBase;
045: import org.apache.fop.fo.FONode;
046: import org.apache.fop.fo.FObj;
047:
048: /**
049: * LayoutManager for a table FO.
050: * A table consists of columns, table header, table footer and multiple
051: * table bodies.
052: * The header, footer and body add the areas created from the table cells.
053: * The table then creates areas for the columns, bodies and rows
054: * the render background.
055: */
056: public class TableLayoutManager extends BlockStackingLayoutManager
057: implements ConditionalElementListener {
058:
059: /**
060: * logging instance
061: */
062: private static Log log = LogFactory
063: .getLog(TableLayoutManager.class);
064:
065: private TableContentLayoutManager contentLM;
066: private ColumnSetup columns = null;
067:
068: private Block curBlockArea;
069:
070: private double tableUnit;
071: private boolean autoLayout = true;
072:
073: private boolean discardBorderBefore;
074: private boolean discardBorderAfter;
075: private boolean discardPaddingBefore;
076: private boolean discardPaddingAfter;
077: private MinOptMax effSpaceBefore;
078: private MinOptMax effSpaceAfter;
079:
080: private int halfBorderSeparationBPD;
081: private int halfBorderSeparationIPD;
082:
083: /**
084: * Create a new table layout manager.
085: * @param node the table FO
086: */
087: public TableLayoutManager(Table node) {
088: super (node);
089: this .columns = new ColumnSetup(node);
090: }
091:
092: /** @return the table FO */
093: public Table getTable() {
094: return (Table) this .fobj;
095: }
096:
097: /**
098: * @return the column setup for this table.
099: */
100: public ColumnSetup getColumns() {
101: return this .columns;
102: }
103:
104: /** @see org.apache.fop.layoutmgr.LayoutManager#initialize() */
105: public void initialize() {
106: foSpaceBefore = new SpaceVal(
107: getTable().getCommonMarginBlock().spaceBefore, this )
108: .getSpace();
109: foSpaceAfter = new SpaceVal(
110: getTable().getCommonMarginBlock().spaceAfter, this )
111: .getSpace();
112: startIndent = getTable().getCommonMarginBlock().startIndent
113: .getValue(this );
114: endIndent = getTable().getCommonMarginBlock().endIndent
115: .getValue(this );
116:
117: if (getTable().isSeparateBorderModel()) {
118: this .halfBorderSeparationBPD = getTable()
119: .getBorderSeparation().getBPD().getLength()
120: .getValue(this ) / 2;
121: this .halfBorderSeparationIPD = getTable()
122: .getBorderSeparation().getIPD().getLength()
123: .getValue(this ) / 2;
124: } else {
125: this .halfBorderSeparationBPD = 0;
126: this .halfBorderSeparationIPD = 0;
127: }
128:
129: if (!getTable().isAutoLayout()
130: && getTable().getInlineProgressionDimension()
131: .getOptimum(this ).getEnum() != EN_AUTO) {
132: autoLayout = false;
133: }
134: }
135:
136: private void resetSpaces() {
137: this .discardBorderBefore = false;
138: this .discardBorderAfter = false;
139: this .discardPaddingBefore = false;
140: this .discardPaddingAfter = false;
141: this .effSpaceBefore = null;
142: this .effSpaceAfter = null;
143: }
144:
145: /**
146: * @return half the value of border-separation.block-progression-dimension, or 0 if
147: * border-collapse="collapse".
148: */
149: public int getHalfBorderSeparationBPD() {
150: return halfBorderSeparationBPD;
151: }
152:
153: /**
154: * @return half the value of border-separation.inline-progression-dimension, or 0 if
155: * border-collapse="collapse".
156: */
157: public int getHalfBorderSeparationIPD() {
158: return halfBorderSeparationIPD;
159: }
160:
161: /**
162: * Handles the Knuth elements at the table level: mainly breaks, spaces and borders
163: * before and after the table. The Knuth elements for the table cells are handled by
164: * TableContentLayoutManager.
165: *
166: * @see org.apache.fop.layoutmgr.LayoutManager
167: * @see TableContentLayoutManager#getNextKnuthElements(LayoutContext, int)
168: */
169: public LinkedList getNextKnuthElements(LayoutContext context,
170: int alignment) {
171:
172: LinkedList returnList = new LinkedList();
173:
174: if (!breakBeforeServed) {
175: breakBeforeServed = true;
176: if (addKnuthElementsForBreakBefore(returnList, context)) {
177: return returnList;
178: }
179: }
180:
181: /*
182: * Compute the IPD and adjust it if necessary (overconstrained)
183: */
184: referenceIPD = context.getRefIPD();
185: if (getTable().getInlineProgressionDimension().getOptimum(this )
186: .getEnum() != EN_AUTO) {
187: int contentIPD = getTable().getInlineProgressionDimension()
188: .getOptimum(this ).getLength().getValue(this );
189: updateContentAreaIPDwithOverconstrainedAdjust(contentIPD);
190: } else {
191: if (!getTable().isAutoLayout()) {
192: log.info("table-layout=\"fixed\" and width=\"auto\", "
193: + "but auto-layout not supported "
194: + "=> assuming width=\"100%\"");
195: }
196: updateContentAreaIPDwithOverconstrainedAdjust();
197: }
198: int sumOfColumns = columns.getSumOfColumnWidths(this );
199: if (!autoLayout && sumOfColumns > getContentAreaIPD()) {
200: log
201: .debug(FONode
202: .decorateWithContextInfo(
203: "The sum of all column widths is larger than the specified table width.",
204: getTable()));
205: updateContentAreaIPDwithOverconstrainedAdjust(sumOfColumns);
206: }
207: int availableIPD = referenceIPD - getIPIndents();
208: if (getContentAreaIPD() > availableIPD) {
209: log
210: .warn(FONode
211: .decorateWithContextInfo(
212: "The extent in inline-progression-direction (width) of a table is"
213: + " bigger than the available space ("
214: + getContentAreaIPD()
215: + "mpt > "
216: + context.getRefIPD()
217: + "mpt)", getTable()));
218: }
219:
220: /* initialize unit to determine computed values
221: * for proportional-column-width()
222: */
223: if (tableUnit == 0.0) {
224: this .tableUnit = columns.computeTableUnit(this );
225: }
226:
227: if (!firstVisibleMarkServed) {
228: addKnuthElementsForSpaceBefore(returnList, alignment);
229: }
230:
231: if (getTable().isSeparateBorderModel()) {
232: addKnuthElementsForBorderPaddingBefore(returnList,
233: !firstVisibleMarkServed);
234: firstVisibleMarkServed = true;
235: // Border and padding to be repeated at each break
236: // This must be done only in the separate-border model, as in collapsing
237: // tables have no padding and borders are determined at the cell level
238: addPendingMarks(context);
239: }
240:
241: // Elements for the table-header/footer/body
242: LinkedList contentKnuthElements = null;
243: LinkedList contentList = new LinkedList();
244: //Position returnPosition = new NonLeafPosition(this, null);
245: //Body prevLM = null;
246:
247: LayoutContext childLC = new LayoutContext(0);
248: /*
249: childLC.setStackLimit(
250: MinOptMax.subtract(context.getStackLimit(),
251: stackSize));*/
252: childLC.setRefIPD(context.getRefIPD());
253: childLC.copyPendingMarksFrom(context);
254:
255: if (contentLM == null) {
256: contentLM = new TableContentLayoutManager(this );
257: }
258: contentKnuthElements = contentLM.getNextKnuthElements(childLC,
259: alignment);
260: if (childLC.isKeepWithNextPending()) {
261: log.debug("TableContentLM signals pending keep-with-next");
262: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
263: }
264: if (childLC.isKeepWithPreviousPending()) {
265: log
266: .debug("TableContentLM signals pending keep-with-previous");
267: context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
268: }
269:
270: // Check if the table's content starts/ends with a forced break
271: // TODO this is hacky and will need to be handled better eventually
272: if (contentKnuthElements.size() > 0) {
273: ListElement element = (ListElement) contentKnuthElements
274: .getFirst();
275: if (element.isForcedBreak()) {
276: // The first row of the table(-body), or (the content of) one of its cells
277: // has a forced break-before
278: int breakBeforeTable = ((Table) fobj).getBreakBefore();
279: if (breakBeforeTable == EN_PAGE
280: || breakBeforeTable == EN_COLUMN
281: || breakBeforeTable == EN_EVEN_PAGE
282: || breakBeforeTable == EN_ODD_PAGE) {
283: // There is already a forced break before the table; remove this one
284: // to prevent a double break
285: contentKnuthElements.removeFirst();
286: } else {
287: element
288: .setPosition(new NonLeafPosition(this , null));
289: }
290: }
291: element = (ListElement) contentKnuthElements.getLast();
292: if (element.isForcedBreak()) {
293: // The last row of the table(-body), or (the content of) one of its cells
294: // has a forced break-after
295: element.setPosition(new NonLeafPosition(this , null));
296: }
297: }
298:
299: //Set index values on elements coming from the content LM
300: Iterator iter = contentKnuthElements.iterator();
301: while (iter.hasNext()) {
302: ListElement el = (ListElement) iter.next();
303: notifyPos(el.getPosition());
304: }
305: log.debug(contentKnuthElements);
306: contentList.addAll(contentKnuthElements);
307: wrapPositionElements(contentList, returnList);
308: if (getTable().isSeparateBorderModel()) {
309: addKnuthElementsForBorderPaddingAfter(returnList, true);
310: }
311: addKnuthElementsForSpaceAfter(returnList, alignment);
312: addKnuthElementsForBreakAfter(returnList, context);
313: if (mustKeepWithNext()) {
314: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
315: }
316: if (mustKeepWithPrevious()) {
317: context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
318: }
319: setFinished(true);
320: resetSpaces();
321: return returnList;
322: }
323:
324: /**
325: * The table area is a reference area that contains areas for
326: * columns, bodies, rows and the contents are in cells.
327: *
328: * @param parentIter the position iterator
329: * @param layoutContext the layout context for adding areas
330: */
331: public void addAreas(PositionIterator parentIter,
332: LayoutContext layoutContext) {
333: getParentArea(null);
334: getPSLM().addIDToPage(getTable().getId());
335:
336: // add space before, in order to implement display-align = "center" or "after"
337: if (layoutContext.getSpaceBefore() != 0) {
338: addBlockSpacing(0.0, new MinOptMax(layoutContext
339: .getSpaceBefore()));
340: }
341:
342: int startXOffset = getTable().getCommonMarginBlock().startIndent
343: .getValue(this );
344:
345: // add column, body then row areas
346:
347: // BPD of the table, i.e., height of its content; table's borders and paddings not counted
348: int tableHeight = 0;
349: //Body childLM;
350: LayoutContext lc = new LayoutContext(0);
351:
352: lc.setRefIPD(getContentAreaIPD());
353: contentLM.setStartXOffset(startXOffset);
354: contentLM.addAreas(parentIter, lc);
355: tableHeight += contentLM.getUsedBPD();
356:
357: curBlockArea.setBPD(tableHeight);
358:
359: if (getTable().isSeparateBorderModel()) {
360: TraitSetter.addBorders(curBlockArea, getTable()
361: .getCommonBorderPaddingBackground(),
362: discardBorderBefore, discardBorderAfter, false,
363: false, this );
364: TraitSetter.addPadding(curBlockArea, getTable()
365: .getCommonBorderPaddingBackground(),
366: discardPaddingBefore, discardPaddingAfter, false,
367: false, this );
368: }
369: TraitSetter.addBackground(curBlockArea, getTable()
370: .getCommonBorderPaddingBackground(), this );
371: TraitSetter.addMargins(curBlockArea, getTable()
372: .getCommonBorderPaddingBackground(), startIndent,
373: endIndent, this );
374: TraitSetter
375: .addBreaks(curBlockArea, getTable().getBreakBefore(),
376: getTable().getBreakAfter());
377: TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext
378: .getSpaceAdjust(), effSpaceBefore, effSpaceAfter);
379:
380: flush();
381:
382: resetSpaces();
383: curBlockArea = null;
384:
385: getPSLM().notifyEndOfLayout(((Table) getFObj()).getId());
386: }
387:
388: /**
389: * Return an Area which can contain the passed childArea. The childArea
390: * may not yet have any content, but it has essential traits set.
391: * In general, if the LayoutManager already has an Area it simply returns
392: * it. Otherwise, it makes a new Area of the appropriate class.
393: * It gets a parent area for its area by calling its parent LM.
394: * Finally, based on the dimensions of the parent area, it initializes
395: * its own area. This includes setting the content IPD and the maximum
396: * BPD.
397: *
398: * @param childArea the child area
399: * @return the parent area of the child
400: */
401: public Area getParentArea(Area childArea) {
402: if (curBlockArea == null) {
403: curBlockArea = new Block();
404: // Set up dimensions
405: // Must get dimensions from parent area
406: /*Area parentArea =*/parentLM.getParentArea(curBlockArea);
407:
408: TraitSetter.setProducerID(curBlockArea, getTable().getId());
409:
410: curBlockArea.setIPD(getContentAreaIPD());
411:
412: setCurrentArea(curBlockArea);
413: }
414: return curBlockArea;
415: }
416:
417: /**
418: * Add the child area to this layout manager.
419: *
420: * @param childArea the child area to add
421: */
422: public void addChildArea(Area childArea) {
423: if (curBlockArea != null) {
424: curBlockArea.addBlock((Block) childArea);
425: }
426: }
427:
428: /**
429: * Reset the position of this layout manager.
430: *
431: * @param resetPos the position to reset to
432: */
433: public void resetPosition(Position resetPos) {
434: if (resetPos == null) {
435: reset(null);
436: }
437: }
438:
439: /** @see org.apache.fop.layoutmgr.BlockLevelLayoutManager */
440: public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
441: // TODO Auto-generated method stub
442: return 0;
443: }
444:
445: /** @see org.apache.fop.layoutmgr.BlockLevelLayoutManager */
446: public void discardSpace(KnuthGlue spaceGlue) {
447: // TODO Auto-generated method stub
448:
449: }
450:
451: /**
452: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether()
453: */
454: public boolean mustKeepTogether() {
455: //TODO Keeps will have to be more sophisticated sooner or later
456: return super .mustKeepTogether()
457: || !getTable().getKeepTogether().getWithinPage()
458: .isAuto()
459: || !getTable().getKeepTogether().getWithinColumn()
460: .isAuto();
461: }
462:
463: /**
464: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious()
465: */
466: public boolean mustKeepWithPrevious() {
467: return !getTable().getKeepWithPrevious().getWithinPage()
468: .isAuto()
469: || !getTable().getKeepWithPrevious().getWithinColumn()
470: .isAuto();
471: }
472:
473: /**
474: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext()
475: */
476: public boolean mustKeepWithNext() {
477: return !getTable().getKeepWithNext().getWithinPage().isAuto()
478: || !getTable().getKeepWithNext().getWithinColumn()
479: .isAuto();
480: }
481:
482: // --------- Property Resolution related functions --------- //
483:
484: /**
485: * @see org.apache.fop.datatypes.PercentBaseContext#getBaseLength(int, FObj)
486: */
487: public int getBaseLength(int lengthBase, FObj fobj) {
488: // Special handler for TableColumn width specifications
489: if (fobj instanceof TableColumn
490: && fobj.getParent() == getFObj()) {
491: switch (lengthBase) {
492: case LengthBase.CONTAINING_BLOCK_WIDTH:
493: return getContentAreaIPD();
494: case LengthBase.TABLE_UNITS:
495: return (int) this .tableUnit;
496: default:
497: log.error("Unknown base type for LengthBase.");
498: return 0;
499: }
500: } else {
501: switch (lengthBase) {
502: case LengthBase.TABLE_UNITS:
503: return (int) this .tableUnit;
504: default:
505: return super .getBaseLength(lengthBase, fobj);
506: }
507: }
508: }
509:
510: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
511: public void notifySpace(RelSide side, MinOptMax effectiveLength) {
512: if (RelSide.BEFORE == side) {
513: if (log.isDebugEnabled()) {
514: log
515: .debug(this + ": Space " + side + ", "
516: + this .effSpaceBefore + "-> "
517: + effectiveLength);
518: }
519: this .effSpaceBefore = effectiveLength;
520: } else {
521: if (log.isDebugEnabled()) {
522: log.debug(this + ": Space " + side + ", "
523: + this .effSpaceAfter + "-> " + effectiveLength);
524: }
525: this .effSpaceAfter = effectiveLength;
526: }
527: }
528:
529: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
530: public void notifyBorder(RelSide side, MinOptMax effectiveLength) {
531: if (effectiveLength == null) {
532: if (RelSide.BEFORE == side) {
533: this .discardBorderBefore = true;
534: } else {
535: this .discardBorderAfter = true;
536: }
537: }
538: if (log.isDebugEnabled()) {
539: log.debug(this + ": Border " + side + " -> "
540: + effectiveLength);
541: }
542: }
543:
544: /** @see org.apache.fop.layoutmgr.ConditionalElementListener */
545: public void notifyPadding(RelSide side, MinOptMax effectiveLength) {
546: if (effectiveLength == null) {
547: if (RelSide.BEFORE == side) {
548: this .discardPaddingBefore = true;
549: } else {
550: this .discardPaddingAfter = true;
551: }
552: }
553: if (log.isDebugEnabled()) {
554: log.debug(this + ": Padding " + side + " -> "
555: + effectiveLength);
556: }
557: }
558:
559: }
|