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: TableCellLayoutManager.java 530887 2007-04-20 19:09:51Z vhennebert $ */
019:
020: package org.apache.fop.layoutmgr.table;
021:
022: import java.util.LinkedList;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.apache.fop.datatypes.PercentBaseContext;
027: import org.apache.fop.fo.FONode;
028: import org.apache.fop.fo.flow.Table;
029: import org.apache.fop.fo.flow.TableCell;
030: import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
031: import org.apache.fop.layoutmgr.AreaAdditionUtil;
032: import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
033: import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
034: import org.apache.fop.layoutmgr.BreakElement;
035: import org.apache.fop.layoutmgr.KnuthElement;
036: import org.apache.fop.layoutmgr.KnuthGlue;
037: import org.apache.fop.layoutmgr.KnuthPenalty;
038: import org.apache.fop.layoutmgr.LayoutContext;
039: import org.apache.fop.layoutmgr.ListElement;
040: import org.apache.fop.layoutmgr.PositionIterator;
041: import org.apache.fop.layoutmgr.Position;
042: import org.apache.fop.layoutmgr.SpaceResolver;
043: import org.apache.fop.layoutmgr.TraitSetter;
044: import org.apache.fop.area.Area;
045: import org.apache.fop.area.Block;
046: import org.apache.fop.area.Trait;
047: import org.apache.fop.traits.MinOptMax;
048:
049: /**
050: * LayoutManager for a table-cell FO.
051: * A cell contains blocks. These blocks fill the cell.
052: */
053: public class TableCellLayoutManager extends BlockStackingLayoutManager
054: implements BlockLevelLayoutManager {
055:
056: /**
057: * logging instance
058: */
059: private static Log log = LogFactory
060: .getLog(TableCellLayoutManager.class);
061:
062: private PrimaryGridUnit primaryGridUnit;
063:
064: private Block curBlockArea;
065:
066: private int inRowIPDOffset;
067:
068: private int xoffset;
069: private int yoffset;
070: private int cellIPD;
071: private int rowHeight;
072: private int usedBPD;
073: private int startBorderWidth;
074: private int endBorderWidth;
075: private int borderAndPaddingBPD;
076: private boolean emptyCell = true;
077:
078: /**
079: * Create a new Cell layout manager.
080: * @param node table-cell FO for which to create the LM
081: * @param pgu primary grid unit for the cell
082: */
083: public TableCellLayoutManager(TableCell node, PrimaryGridUnit pgu) {
084: super (node);
085: fobj = node;
086: this .primaryGridUnit = pgu;
087: }
088:
089: /** @return the table-cell FO */
090: public TableCell getTableCell() {
091: return (TableCell) this .fobj;
092: }
093:
094: private boolean isSeparateBorderModel() {
095: return getTable().isSeparateBorderModel();
096: }
097:
098: /** @see org.apache.fop.layoutmgr.LayoutManager#initialize() */
099: public void initialize() {
100: borderAndPaddingBPD = 0;
101: borderAndPaddingBPD += getTableCell()
102: .getCommonBorderPaddingBackground()
103: .getBorderBeforeWidth(false);
104: borderAndPaddingBPD += getTableCell()
105: .getCommonBorderPaddingBackground()
106: .getBorderAfterWidth(false);
107: if (!isSeparateBorderModel()) {
108: borderAndPaddingBPD /= 2;
109: }
110: borderAndPaddingBPD += getTableCell()
111: .getCommonBorderPaddingBackground().getPaddingBefore(
112: false, this );
113: borderAndPaddingBPD += getTableCell()
114: .getCommonBorderPaddingBackground().getPaddingAfter(
115: false, this );
116: }
117:
118: /**
119: * @return the table owning this cell
120: */
121: public Table getTable() {
122: FONode node = fobj.getParent();
123: while (!(node instanceof Table)) {
124: node = node.getParent();
125: }
126: return (Table) node;
127: }
128:
129: /** @see org.apache.fop.layoutmgr.BlockStackingLayoutManager#getIPIndents() */
130: protected int getIPIndents() {
131: int iIndents = 0;
132: int[] startEndBorderWidths = primaryGridUnit
133: .getStartEndBorderWidths();
134: startBorderWidth += startEndBorderWidths[0];
135: endBorderWidth += startEndBorderWidths[1];
136: iIndents += startBorderWidth;
137: iIndents += endBorderWidth;
138: if (!isSeparateBorderModel()) {
139: iIndents /= 2;
140: }
141: iIndents += getTableCell().getCommonBorderPaddingBackground()
142: .getPaddingStart(false, this );
143: iIndents += getTableCell().getCommonBorderPaddingBackground()
144: .getPaddingEnd(false, this );
145: return iIndents;
146: }
147:
148: /**
149: * @see org.apache.fop.layoutmgr.LayoutManager
150: */
151: public LinkedList getNextKnuthElements(LayoutContext context,
152: int alignment) {
153: MinOptMax stackLimit = new MinOptMax(context.getStackLimit());
154:
155: referenceIPD = context.getRefIPD();
156: cellIPD = referenceIPD;
157: cellIPD -= getIPIndents();
158: if (isSeparateBorderModel()) {
159: int borderSep = getTable().getBorderSeparation()
160: .getLengthPair().getIPD().getLength()
161: .getValue(this );
162: cellIPD -= borderSep;
163: }
164:
165: LinkedList returnedList = null;
166: LinkedList contentList = new LinkedList();
167: LinkedList returnList = new LinkedList();
168:
169: BlockLevelLayoutManager curLM; // currently active LM
170: BlockLevelLayoutManager prevLM = null; // previously active LM
171: while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) {
172: LayoutContext childLC = new LayoutContext(0);
173: // curLM is a ?
174: childLC.setStackLimit(MinOptMax.subtract(context
175: .getStackLimit(), stackLimit));
176: childLC.setRefIPD(cellIPD);
177:
178: // get elements from curLM
179: returnedList = curLM.getNextKnuthElements(childLC,
180: alignment);
181: if (childLC.isKeepWithNextPending()) {
182: log.debug("child LM signals pending keep with next");
183: }
184: if (contentList.size() == 0
185: && childLC.isKeepWithPreviousPending()) {
186: context
187: .setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
188: childLC
189: .setFlags(
190: LayoutContext.KEEP_WITH_PREVIOUS_PENDING,
191: false);
192: }
193:
194: if (prevLM != null) {
195: // there is a block handled by prevLM
196: // before the one handled by curLM
197: if (mustKeepTogether()
198: || context.isKeepWithNextPending()
199: || childLC.isKeepWithPreviousPending()) {
200: //Clear keep pending flag
201: context
202: .setFlags(
203: LayoutContext.KEEP_WITH_NEXT_PENDING,
204: false);
205: childLC.setFlags(
206: LayoutContext.KEEP_WITH_PREVIOUS_PENDING,
207: false);
208: // add an infinite penalty to forbid a break between
209: // blocks
210: contentList.add(new BreakElement(
211: new Position(this ), KnuthElement.INFINITE,
212: context));
213: //contentList.add(new KnuthPenalty(0,
214: // KnuthElement.INFINITE, false,
215: // new Position(this), false));
216: } else if (!(((ListElement) contentList.getLast())
217: .isGlue()
218: || (((ListElement) contentList.getLast())
219: .isPenalty() && ((KnuthPenalty) contentList
220: .getLast()).getP() < KnuthElement.INFINITE) || (contentList
221: .getLast() instanceof BreakElement && ((BreakElement) contentList
222: .getLast()).getPenaltyValue() < KnuthElement.INFINITE))) {
223: // TODO vh: this is hacky
224: // The getNextKnuthElements method of TableCellLM must not be called
225: // twice, otherwise some settings like indents or borders will be
226: // counted several times and lead to a wrong output. Anyway the
227: // getNextKnuthElements methods should be called only once eventually
228: // (i.e., when multi-threading the code), even when there are forced
229: // breaks.
230: // If we add a break possibility after a forced break the
231: // AreaAdditionUtil.addAreas method will act on a sequence starting
232: // with a SpaceResolver.SpaceHandlingBreakPosition element, having no
233: // LM associated to it. Thus it will stop early instead of adding
234: // areas for following Positions. The above test aims at preventing
235: // such a situation from occuring. add a null penalty to allow a break
236: // between blocks
237: contentList.add(new BreakElement(
238: new Position(this ), 0, context));
239: //contentList.add(new KnuthPenalty(0, 0, false,
240: // new Position(this), false));
241: } else {
242: // the last element in contentList is a feasible breakpoint, there is
243: // no need to add a penalty
244: }
245: }
246: contentList.addAll(returnedList);
247: if (returnedList.size() == 0) {
248: //Avoid NoSuchElementException below (happens with empty blocks)
249: continue;
250: }
251: if (childLC.isKeepWithNextPending()) {
252: //Clear and propagate
253: childLC.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING,
254: false);
255: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
256: }
257: prevLM = curLM;
258: }
259:
260: returnedList = new LinkedList();
261: wrapPositionElements(contentList, returnList);
262:
263: //Space resolution
264: SpaceResolver.resolveElementList(returnList);
265:
266: getPSLM().notifyEndOfLayout(((TableCell) getFObj()).getId());
267:
268: setFinished(true);
269: return returnList;
270: }
271:
272: /**
273: * Set the y offset of this cell.
274: * This offset is used to set the absolute position of the cell.
275: *
276: * @param off the y direction offset
277: */
278: public void setYOffset(int off) {
279: yoffset = off;
280: }
281:
282: /**
283: * Set the x offset of this cell (usually the same as its parent row).
284: * This offset is used to determine the absolute position of the cell.
285: *
286: * @param off the x offset
287: */
288: public void setXOffset(int off) {
289: xoffset = off;
290: }
291:
292: /**
293: * Set the IPD offset of this cell inside the table-row.
294: * This offset is used to determine the absolute position of the cell.
295: * @param off the IPD offset
296: */
297: public void setInRowIPDOffset(int off) {
298: this .inRowIPDOffset = off;
299: }
300:
301: /**
302: * Set the content height for this cell. This method is used during
303: * addAreas() stage.
304: *
305: * @param h the height of the contents of this cell
306: */
307: public void setContentHeight(int h) {
308: usedBPD = h;
309: }
310:
311: /**
312: * Set the row height that contains this cell. This method is used during
313: * addAreas() stage.
314: *
315: * @param h the height of the row
316: */
317: public void setRowHeight(int h) {
318: rowHeight = h;
319: }
320:
321: /**
322: * Returns the bpd of the given grid unit.
323: * @param gu a grid unit belonging to this cell
324: * @return the content height of the grid unit
325: */
326: private int getContentHeight(GridUnit gu) {
327: int bpd = rowHeight;
328: if (isSeparateBorderModel()) {
329: bpd -= gu.getPrimary().getBorders().getBorderBeforeWidth(
330: false);
331: bpd -= gu.getPrimary().getBorders().getBorderAfterWidth(
332: false);
333: } else {
334: bpd -= gu.getPrimary().getHalfMaxBorderWidth();
335: }
336: CommonBorderPaddingBackground cbpb = gu.getCell()
337: .getCommonBorderPaddingBackground();
338: bpd -= cbpb.getPaddingBefore(false, this );
339: bpd -= cbpb.getPaddingAfter(false, this );
340: bpd -= 2 * ((TableLayoutManager) getParent())
341: .getHalfBorderSeparationBPD();
342: return bpd;
343: }
344:
345: /**
346: * Add the areas for the break points. The cell contains block stacking layout
347: * managers that add block areas.
348: *
349: * <p>In the collapsing-border model, the borders of a cell that spans over several
350: * rows or columns are drawn separately for each grid unit. Therefore we must know the
351: * height of each grid row spanned over by the cell. Also, if the cell is broken over
352: * two pages we must know which spanned grid rows are present on the current page.</p>
353: *
354: * @param parentIter the iterator of the break positions
355: * @param layoutContext the layout context for adding the areas
356: * @param spannedGridRowHeights in collapsing-border model for a spanning cell, height
357: * of each spanned grid row
358: * @param startRow first grid row on the current page spanned over by the cell,
359: * inclusive
360: * @param endRow last grid row on the current page spanned over by the cell, exclusive
361: */
362: public void addAreas(PositionIterator parentIter,
363: LayoutContext layoutContext, int[] spannedGridRowHeights,
364: int startRow, int endRow) {
365: getParentArea(null);
366:
367: getPSLM().addIDToPage(getTableCell().getId());
368:
369: if (isSeparateBorderModel()) {
370: if (!emptyCell || getTableCell().showEmptyCells()) {
371: TraitSetter.addBorders(curBlockArea, getTableCell()
372: .getCommonBorderPaddingBackground(), false,
373: false, false, false, this );
374: TraitSetter.addPadding(curBlockArea, getTableCell()
375: .getCommonBorderPaddingBackground(), false,
376: false, false, false, this );
377: }
378: } else {
379: if (!primaryGridUnit.hasSpanning()) {
380: //Can set the borders directly if there's no span
381: boolean[] outer = new boolean[] {
382: primaryGridUnit
383: .getFlag(GridUnit.FIRST_IN_TABLE),
384: primaryGridUnit.getFlag(GridUnit.LAST_IN_TABLE),
385: primaryGridUnit
386: .getFlag(GridUnit.IN_FIRST_COLUMN),
387: primaryGridUnit
388: .getFlag(GridUnit.IN_LAST_COLUMN) };
389: TraitSetter.addCollapsingBorders(curBlockArea,
390: primaryGridUnit.getBorders(), outer, this );
391: } else {
392: boolean[] outer = new boolean[4];
393: int dy = yoffset;
394: for (int y = startRow; y < endRow; y++) {
395: GridUnit[] gridUnits = (GridUnit[]) primaryGridUnit
396: .getRows().get(y);
397: int dx = xoffset;
398: for (int x = 0; x < gridUnits.length; x++) {
399: GridUnit gu = gridUnits[x];
400: if (gu.hasBorders()) {
401: //Blocks for painting grid unit borders
402: Block block = new Block();
403: block.addTrait(Trait.IS_REFERENCE_AREA,
404: Boolean.TRUE);
405: block.setPositioning(Block.ABSOLUTE);
406:
407: int bpd = spannedGridRowHeights[y
408: - startRow];
409: bpd -= gu.getBorders()
410: .getBorderBeforeWidth(false) / 2;
411: bpd -= gu.getBorders().getBorderAfterWidth(
412: false) / 2;
413: block.setBPD(bpd);
414: if (log.isTraceEnabled()) {
415: log.trace("pgu: "
416: + primaryGridUnit
417: + "; gu: "
418: + gu
419: + "; yoffset: "
420: + (dy - gu.getBorders()
421: .getBorderBeforeWidth(
422: false) / 2)
423: + "; bpd: " + bpd);
424: }
425: int ipd = gu
426: .getColumn()
427: .getColumnWidth()
428: .getValue(
429: (PercentBaseContext) getParent());
430: int borderStartWidth = gu.getBorders()
431: .getBorderStartWidth(false) / 2;
432: ipd -= borderStartWidth;
433: ipd -= gu.getBorders().getBorderEndWidth(
434: false) / 2;
435: block.setIPD(ipd);
436: block.setXOffset(dx + borderStartWidth);
437: block
438: .setYOffset(dy
439: - gu
440: .getBorders()
441: .getBorderBeforeWidth(
442: false) / 2);
443: outer[0] = gu
444: .getFlag(GridUnit.FIRST_IN_TABLE);
445: outer[1] = gu
446: .getFlag(GridUnit.LAST_IN_TABLE);
447: outer[2] = gu
448: .getFlag(GridUnit.IN_FIRST_COLUMN);
449: outer[3] = gu
450: .getFlag(GridUnit.IN_LAST_COLUMN);
451: TraitSetter.addCollapsingBorders(block, gu
452: .getBorders(), outer, this );
453: parentLM.addChildArea(block);
454: }
455: dx += gu.getColumn().getColumnWidth().getValue(
456: (PercentBaseContext) getParent());
457: }
458: dy += spannedGridRowHeights[y - startRow];
459: }
460: }
461: }
462: TraitSetter.addPadding(curBlockArea, primaryGridUnit
463: .getBorders(), false, false, false, false, this );
464:
465: //Handle display-align
466: int contentBPD = getContentHeight(primaryGridUnit);
467: if (usedBPD < contentBPD) {
468: if (getTableCell().getDisplayAlign() == EN_CENTER) {
469: Block space = new Block();
470: space.setBPD((contentBPD - usedBPD) / 2);
471: curBlockArea.addBlock(space);
472: } else if (getTableCell().getDisplayAlign() == EN_AFTER) {
473: Block space = new Block();
474: space.setBPD((contentBPD - usedBPD));
475: curBlockArea.addBlock(space);
476: }
477: }
478:
479: AreaAdditionUtil.addAreas(this , parentIter, layoutContext);
480:
481: curBlockArea.setBPD(contentBPD);
482:
483: // Add background after we know the BPD
484: if (isSeparateBorderModel()) {
485: if (!emptyCell || getTableCell().showEmptyCells()) {
486: TraitSetter.addBackground(curBlockArea, getTableCell()
487: .getCommonBorderPaddingBackground(), this );
488: }
489: } else {
490: TraitSetter.addBackground(curBlockArea, getTableCell()
491: .getCommonBorderPaddingBackground(), this );
492: }
493:
494: flush();
495:
496: curBlockArea = null;
497: }
498:
499: /**
500: * Return an Area which can contain the passed childArea. The childArea
501: * may not yet have any content, but it has essential traits set.
502: * In general, if the LayoutManager already has an Area it simply returns
503: * it. Otherwise, it makes a new Area of the appropriate class.
504: * It gets a parent area for its area by calling its parent LM.
505: * Finally, based on the dimensions of the parent area, it initializes
506: * its own area. This includes setting the content IPD and the maximum
507: * BPD.
508: *
509: * @param childArea the child area to get the parent for
510: * @return the parent area
511: */
512: public Area getParentArea(Area childArea) {
513: if (curBlockArea == null) {
514: curBlockArea = new Block();
515: curBlockArea
516: .addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
517: TraitSetter.setProducerID(curBlockArea, getTableCell()
518: .getId());
519: curBlockArea.setPositioning(Block.ABSOLUTE);
520: int indent = startBorderWidth;
521: if (!isSeparateBorderModel()) {
522: indent /= 2;
523: }
524: indent += getTableCell().getCommonBorderPaddingBackground()
525: .getPaddingStart(false, this );
526: // set position
527: int borderAdjust = 0;
528: if (!isSeparateBorderModel()) {
529: if (primaryGridUnit.hasSpanning()) {
530: borderAdjust -= primaryGridUnit
531: .getHalfMaxBeforeBorderWidth();
532: } else {
533: borderAdjust += primaryGridUnit
534: .getHalfMaxBeforeBorderWidth();
535: }
536: } else {
537: //borderAdjust += primaryGridUnit.getBorders().getBorderBeforeWidth(false);
538: }
539: TableLayoutManager tableLM = (TableLayoutManager) getParent();
540: curBlockArea.setXOffset(xoffset + inRowIPDOffset
541: + tableLM.getHalfBorderSeparationIPD() + indent);
542: curBlockArea.setYOffset(yoffset - borderAdjust
543: + tableLM.getHalfBorderSeparationBPD());
544: curBlockArea.setIPD(cellIPD);
545: //curBlockArea.setHeight();
546:
547: // Set up dimensions
548: /*Area parentArea =*/parentLM.getParentArea(curBlockArea);
549: // Get reference IPD from parentArea
550: setCurrentArea(curBlockArea); // ??? for generic operations
551: }
552: return curBlockArea;
553: }
554:
555: /**
556: * Add the child to the cell block area.
557: *
558: * @param childArea the child to add to the cell
559: */
560: public void addChildArea(Area childArea) {
561: if (curBlockArea != null) {
562: curBlockArea.addBlock((Block) childArea);
563: }
564: }
565:
566: /**
567: * Reset the position of the layout.
568: *
569: * @param resetPos the position to reset to
570: */
571: public void resetPosition(Position resetPos) {
572: if (resetPos == null) {
573: reset(null);
574: }
575: }
576:
577: /**
578: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager
579: */
580: public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
581: // TODO Auto-generated method stub
582: return 0;
583: }
584:
585: /**
586: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager
587: */
588: public void discardSpace(KnuthGlue spaceGlue) {
589: // TODO Auto-generated method stub
590: }
591:
592: /**
593: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether()
594: */
595: public boolean mustKeepTogether() {
596: //TODO Keeps will have to be more sophisticated sooner or later
597: boolean keep = ((BlockLevelLayoutManager) getParent())
598: .mustKeepTogether();
599: if (primaryGridUnit.getRow() != null) {
600: keep |= primaryGridUnit.getRow().mustKeepTogether();
601: }
602: return keep;
603: }
604:
605: /**
606: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious()
607: */
608: public boolean mustKeepWithPrevious() {
609: return false; //TODO FIX ME
610: /*
611: return !fobj.getKeepWithPrevious().getWithinPage().isAuto()
612: || !fobj.getKeepWithPrevious().getWithinColumn().isAuto();
613: */
614: }
615:
616: /**
617: * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext()
618: */
619: public boolean mustKeepWithNext() {
620: return false; //TODO FIX ME
621: /*
622: return !fobj.getKeepWithNext().getWithinPage().isAuto()
623: || !fobj.getKeepWithNext().getWithinColumn().isAuto();
624: */
625: }
626:
627: // --------- Property Resolution related functions --------- //
628:
629: /**
630: * Returns the IPD of the content area
631: * @return the IPD of the content area
632: */
633: public int getContentAreaIPD() {
634: return cellIPD;
635: }
636:
637: /**
638: * Returns the BPD of the content area
639: * @return the BPD of the content area
640: */
641: public int getContentAreaBPD() {
642: if (curBlockArea != null) {
643: return curBlockArea.getBPD();
644: } else {
645: log.error("getContentAreaBPD called on unknown BPD");
646: return -1;
647: }
648: }
649:
650: /**
651: * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesReferenceArea
652: */
653: public boolean getGeneratesReferenceArea() {
654: return true;
655: }
656:
657: /**
658: * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesBlockArea
659: */
660: public boolean getGeneratesBlockArea() {
661: return true;
662: }
663:
664: }
|