001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * CanvasMajorAxisLayoutStep.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.layout.process;
030:
031: import org.jfree.report.ElementAlignment;
032: import org.jfree.report.layout.model.BlockRenderBox;
033: import org.jfree.report.layout.model.FinishedRenderNode;
034: import org.jfree.report.layout.model.InlineRenderBox;
035: import org.jfree.report.layout.model.LogicalPageBox;
036: import org.jfree.report.layout.model.ParagraphRenderBox;
037: import org.jfree.report.layout.model.RenderBox;
038: import org.jfree.report.layout.model.RenderLength;
039: import org.jfree.report.layout.model.RenderNode;
040: import org.jfree.report.layout.model.RenderableReplacedContent;
041: import org.jfree.report.layout.model.WatermarkAreaBox;
042: import org.jfree.report.layout.model.context.BoxDefinition;
043: import org.jfree.report.layout.model.context.StaticBoxLayoutProperties;
044: import org.jfree.report.style.ElementStyleKeys;
045: import org.jfree.report.util.geom.StrictGeomUtility;
046: import org.jfree.util.Log;
047:
048: /**
049: * This processes the second step of the vertical-layouting.
050: * <p/>
051: * At this point, the static height of all elements is known (that is the height of all elements that do not use
052: * percentages in either the y or height properties).
053: * <p/>
054: * That height is then used as base-value to resolve all relative heights and y positions and the layouting is redone.
055: *
056: * @author Thomas Morgner
057: */
058: public final class CanvasMajorAxisLayoutStep extends
059: IterateVisualProcessStep {
060: // Set the maximum height to an incredibly high value. This is now 2^43 micropoints or more than
061: // 3000 kilometers. Please call me directly at any time if you need more space for printing.
062: private static final long MAX_AUTO = StrictGeomUtility
063: .toInternalValue(0x80000000000L);
064:
065: private RenderBox continuedElement;
066:
067: public CanvasMajorAxisLayoutStep() {
068: }
069:
070: public void compute(final LogicalPageBox pageBox) {
071: this .continuedElement = null;
072: try {
073: startProcessing(pageBox);
074: } finally {
075: this .continuedElement = null;
076: }
077: }
078:
079: private long resolveParentHeight(final RenderNode node) {
080: final RenderBox parent = node.getParent();
081: if (parent == null) {
082: return 0;
083: }
084: return parent.getCachedHeight();
085: }
086:
087: /**
088: * Continues processing. The renderbox must have a valid x-layout (that is: X, content-X1, content-X2 and Width)
089: *
090: * @param box
091: */
092: public void continueComputation(final RenderBox box) {
093: // This is most-likely wrong, but as we do not support inline-block elements yet, we can ignore this for now.
094: if (box.getContentAreaX2() == 0 || box.getCachedWidth() == 0) {
095: throw new IllegalStateException(
096: "Box must be layouted a bit ..");
097: }
098:
099: this .continuedElement = box;
100: startProcessing(box);
101: this .continuedElement = null;
102: }
103:
104: protected boolean startBlockLevelBox(final RenderBox box) {
105: if (box.isIgnorableForRendering()) {
106: return false;
107: }
108:
109: if (box.isCacheValid()) {
110: return false;
111: }
112:
113: final long oldPosition = box.getCachedY();
114:
115: // Compute the block-position of the box. The box is positioned relative to the previous silbling or
116: // relative to the parent.
117: box.setCachedY(computeVerticalBlockPosition(box));
118: if (box instanceof BlockRenderBox) {
119: box.setCachedHeight(computeBlockHeightAndAlign(box,
120: resolveParentHeight(box)));
121: } else {
122:
123: box.setCachedHeight(computeCanvasHeight(box));
124: }
125:
126: if (box instanceof ParagraphRenderBox) {
127: if (box.getCachedY() != oldPosition) {
128: CacheBoxShifter.shiftBoxChilds(box, box.getCachedY()
129: - oldPosition);
130: }
131: }
132:
133: return true;
134: }
135:
136: protected void processBlockLevelNode(final RenderNode node) {
137: // This could be anything, text, or an image.
138: node.setCachedY(computeVerticalBlockPosition(node));
139:
140: if (node instanceof FinishedRenderNode) {
141: final FinishedRenderNode fnode = (FinishedRenderNode) node;
142: node.setCachedHeight(fnode.getLayoutedHeight());
143: } else if (node instanceof InlineRenderBox) {
144: throw new IllegalStateException(
145: "A Inline-Box must be contained in a paragraph.");
146: } else if (node instanceof RenderableReplacedContent) {
147: final RenderableReplacedContent rpc = (RenderableReplacedContent) node;
148: node.setCachedHeight(rpc.computeHeight(
149: computeBlockContextWidth(node), node
150: .getComputedWidth()));
151: }
152: }
153:
154: protected void finishBlockLevelBox(final RenderBox box) {
155: if (box instanceof BlockRenderBox) {
156: // make sure that we resolve against zero.
157: box.setCachedHeight(computeBlockHeightAndAlign(box,
158: resolveParentHeight(box)));
159: } else {
160: box.setCachedHeight(computeCanvasHeight(box));
161: }
162: }
163:
164: private long computeVerticalBlockPosition(final RenderNode node) {
165: // we have no margins yet ..
166: final long marginTop = 0;
167:
168: // The y-position of a box depends on the parent.
169: final RenderBox parent = node.getParent();
170:
171: // A table row is something special. Although it is a block box,
172: // it layouts its children from left to right
173: if (parent instanceof BlockRenderBox) {
174: final RenderNode prev = node.getVisiblePrev();
175: if (prev != null) {
176: // we have a silbling. Position yourself directly below your silbling ..
177: return (marginTop + prev.getCachedY() + prev
178: .getCachedHeight());
179: } else {
180: final StaticBoxLayoutProperties blp = parent
181: .getStaticBoxLayoutProperties();
182: final BoxDefinition bdef = parent.getBoxDefinition();
183: final long insetTop = (blp.getBorderTop() + bdef
184: .getPaddingTop());
185:
186: return (marginTop + insetTop + parent.getCachedY());
187: }
188: } else {
189: // there's no parent ..
190: return (marginTop);
191: }
192: }
193:
194: private long computeBlockHeightAndAlign(final RenderBox box,
195: final long resolveSize) {
196:
197: // For the water-mark area, this computation is different. The Watermark-area uses the known height of
198: // the parent (=the page size)
199: if (box instanceof WatermarkAreaBox) {
200: final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) box;
201: final LogicalPageBox lpb = watermarkAreaBox
202: .getLogicalPage();
203: // set the page-height as watermark size.
204: return lpb.getPageHeight();
205: }
206:
207: // Check the height. Set the height.
208: final BoxDefinition boxDefinition = box.getBoxDefinition();
209: final RenderLength preferredHeight = boxDefinition
210: .getPreferredHeight();
211: final RenderLength minimumHeight = boxDefinition
212: .getMinimumHeight();
213: final RenderLength maximumHeight = boxDefinition
214: .getMaximumHeight();
215:
216: final long usedHeight;
217: final long childY2;
218: final long childY1;
219: final RenderNode lastChildNode = box.getLastChild();
220: if (lastChildNode != null) {
221: childY1 = box.getFirstChild().getCachedY();
222: childY2 = lastChildNode.getCachedY()
223: + lastChildNode.getCachedHeight()
224: + lastChildNode.getEffectiveMarginBottom();
225: usedHeight = (childY2 - childY1);
226: } else {
227: usedHeight = 0;
228: childY2 = 0;
229: childY1 = 0;
230: }
231:
232: //todo
233: final long rminH = minimumHeight.resolve(resolveSize, 0);
234: final long rmaxH = maximumHeight.resolve(resolveSize, MAX_AUTO);
235:
236: // find the maximum of the used height (for all childs) and the specified min-height.
237: final long usableParentHeight = resolveUseableParentHeight(box);
238: final long consumedHeight = Math.max(box.getCachedHeight(),
239: Math.min(rminH, usableParentHeight));
240:
241: final StaticBoxLayoutProperties blp = box
242: .getStaticBoxLayoutProperties();
243: final long insetBottom = blp.getBorderBottom()
244: + boxDefinition.getPaddingBottom();
245: final long insetTop = blp.getBorderTop()
246: + boxDefinition.getPaddingTop();
247:
248: final long computedHeight;
249: if (boxDefinition.isSizeSpecifiesBorderBox()) {
250: final long rprefH = preferredHeight.resolve(resolveSize,
251: usedHeight + insetTop + insetBottom);
252: final long specifiedHeight = InfiniteMajorAxisLayoutStep
253: .computeHeight(rminH, rmaxH, rprefH);
254: computedHeight = Math.min(consumedHeight, specifiedHeight)
255: - insetTop - insetBottom;
256: } else {
257: final long rprefH = preferredHeight.resolve(resolveSize,
258: usedHeight);
259: final long specifiedHeight = InfiniteMajorAxisLayoutStep
260: .computeHeight(rminH, rmaxH, rprefH);
261: computedHeight = Math.min(consumedHeight, specifiedHeight);
262: }
263:
264: if (lastChildNode != null) {
265: // grab the node's y2
266: if (computedHeight > usedHeight) {
267: // we have extra space to distribute. So lets shift some boxes.
268: final ElementAlignment valign = box
269: .getNodeLayoutProperties()
270: .getVerticalAlignment();
271: if (ElementAlignment.BOTTOM.equals(valign)) {
272: final long boxBottom = (box.getCachedY()
273: + box.getCachedHeight() - insetBottom);
274: final long delta = boxBottom - childY2;
275: CacheBoxShifter.shiftBoxChilds(box, delta);
276: } else if (ElementAlignment.MIDDLE.equals(valign)) {
277: final long extraHeight = computedHeight
278: - usedHeight;
279: final long boxTop = box.getCachedY() + insetTop
280: + (extraHeight / 2);
281: final long delta = boxTop - childY1;
282: CacheBoxShifter.shiftBoxChilds(box, delta);
283: }
284: }
285: }
286:
287: return Math.min(usableParentHeight, Math.max(0, computedHeight
288: + insetTop + insetBottom));
289: }
290:
291: private long computeBlockContextWidth(final RenderNode box) {
292: final RenderBox parentBlockContext = box.getParent();
293: if (parentBlockContext == null) {
294: final LogicalPageBox logicalPage = box.getLogicalPage();
295: if (logicalPage == null) {
296: return 0;
297: }
298: return logicalPage.getPageWidth();
299: }
300: return parentBlockContext.getStaticBoxLayoutProperties()
301: .getBlockContextWidth();
302: }
303:
304: protected void processParagraphChilds(final ParagraphRenderBox box) {
305: }
306:
307: protected boolean startCanvasLevelBox(final RenderBox box) {
308: if (box.isCacheValid()) {
309: return false;
310: }
311:
312: if (box.isIgnorableForRendering()) {
313: return false;
314: }
315:
316: final long oldPosition = box.getCachedY();
317: box.setCachedY(computeVerticalCanvasPosition(box));
318: if (box instanceof BlockRenderBox) {
319: // make sure that we resolve against zero.
320: box.setCachedHeight(computeBlockHeightAndAlign(box,
321: resolveParentHeight(box)));
322: } else {
323:
324: box.setCachedHeight(computeCanvasHeight(box));
325: }
326: if (box instanceof ParagraphRenderBox) {
327: if (box.getCachedY() != oldPosition) {
328: CacheBoxShifter.shiftBoxChilds(box, box.getCachedY()
329: - oldPosition);
330: }
331: }
332:
333: return true;
334: }
335:
336: protected void processCanvasLevelNode(final RenderNode node) {
337: node.setCachedY(computeVerticalCanvasPosition(node));
338:
339: // docmark
340: if (node instanceof RenderableReplacedContent) {
341: final RenderableReplacedContent rpc = (RenderableReplacedContent) node;
342: final long computedHeight = rpc.computeHeight(
343: resolveParentHeight(node), node.getComputedWidth());
344: node.setCachedHeight(computedHeight);
345: } else if (node instanceof FinishedRenderNode) {
346: final FinishedRenderNode fnode = (FinishedRenderNode) node;
347: node.setCachedHeight(fnode.getLayoutedHeight());
348: } else {
349: node.setCachedHeight(0);
350: }
351: }
352:
353: /**
354: * Finishes up a canvas level box. This updates/affects the height of the parent, as the canvas model defines that the
355: * parent always fully encloses all of its childs.
356: * <p/>
357: * When no preferred height is defined, the height of an element is the maximum of its minimum-height and the absolute
358: * height of all of its direct children.
359: * <p/>
360: * To resolve the value of percentages, the system uses the maximum of the parent's height and the maximum of all (y +
361: * height) of all children.)
362: *
363: * @param box
364: */
365: protected void finishCanvasLevelBox(final RenderBox box) {
366: if (box instanceof BlockRenderBox) {
367: // make sure that we resolve against zero.
368: box.setCachedHeight(computeBlockHeightAndAlign(box,
369: resolveParentHeight(box)));
370: } else {
371:
372: box.setCachedHeight(computeCanvasHeight(box));
373: }
374: }
375:
376: private long computeVerticalCanvasPosition(final RenderNode node) {
377: final RenderBox parent = node.getParent();
378: final long parentPosition;
379: if (parent == null) {
380: parentPosition = 0;
381: } else {
382: final StaticBoxLayoutProperties blp = parent
383: .getStaticBoxLayoutProperties();
384: final BoxDefinition bdef = parent.getBoxDefinition();
385: final long insetsTop = (blp.getBorderTop() + bdef
386: .getPaddingTop());
387: parentPosition = parent.getCachedY() + insetsTop;
388: }
389:
390: final double posY = node.getStyleSheet()
391: .getDoubleStyleProperty(ElementStyleKeys.POS_Y, 0);
392: if (node.isSizeSpecifiesBorderBox()) {
393: return (parentPosition + RenderLength.resolveLength(
394: resolveParentHeight(node), posY));
395: } else {
396: final long insetsTop;
397: if (node instanceof RenderBox) {
398: final RenderBox box = (RenderBox) node;
399: final StaticBoxLayoutProperties blp = box
400: .getStaticBoxLayoutProperties();
401: final BoxDefinition bdef = box.getBoxDefinition();
402: insetsTop = (blp.getBorderTop() + bdef.getPaddingTop());
403: } else {
404: insetsTop = 0;
405: }
406: return (parentPosition
407: + RenderLength.resolveLength(
408: resolveParentHeight(node), posY) - insetsTop);
409: }
410: }
411:
412: private long resolveUseableParentHeight(final RenderNode node) {
413: final RenderBox parent = node.getParent();
414: if (parent == null) {
415: return node.getCachedHeight();
416: }
417: final long height = parent.getCachedHeight();
418: final BoxDefinition bdef = parent.getBoxDefinition();
419: final StaticBoxLayoutProperties blp = parent
420: .getStaticBoxLayoutProperties();
421: final long insetsTop = (blp.getBorderTop() + bdef
422: .getPaddingTop());
423: final long insetsBottom = blp.getBorderBottom()
424: + bdef.getPaddingBottom();
425: return (parent.getCachedY() + height - insetsTop - insetsBottom)
426: - node.getCachedY();
427: }
428:
429: //todo
430: private long computeCanvasHeight(final RenderBox box) {
431: final StaticBoxLayoutProperties blp = box
432: .getStaticBoxLayoutProperties();
433: final BoxDefinition bdef = box.getBoxDefinition();
434:
435: final BoxDefinition boxDefinition = box.getBoxDefinition();
436: final RenderLength minHeight = boxDefinition.getMinimumHeight();
437: final RenderLength preferredHeight = boxDefinition
438: .getPreferredHeight();
439: final RenderLength maxHeight = boxDefinition.getMaximumHeight();
440:
441: final long insetsTop = (blp.getBorderTop() + bdef
442: .getPaddingTop());
443: final long insetsBottom = blp.getBorderBottom()
444: + bdef.getPaddingBottom();
445: final long insets = insetsTop + insetsBottom;
446:
447: final long parentHeight = Math.max(resolveParentHeight(box),
448: box.getCachedHeight());
449: // find the maximum of the used height (for all childs) and the specified min-height.
450: final long usableParentHeight = resolveUseableParentHeight(box);
451: long consumedHeight = Math.max(box.getCachedHeight(), Math.min(
452: minHeight.resolve(parentHeight), usableParentHeight));
453:
454: // The consumed height computed above specifies the size at the border-edge.
455: // However, depending on the box-sizing property, we may have to resolve them against the
456: // content-edge instead.
457:
458: if (box.isSizeSpecifiesBorderBox()) {
459: final long minHeightResolved = minHeight
460: .resolve(parentHeight);
461: final long maxHeightResolved = maxHeight
462: .resolve(parentHeight);
463: final long prefHeightResolved;
464: if (preferredHeight == RenderLength.AUTO) {
465: prefHeightResolved = consumedHeight;
466: } else {
467: prefHeightResolved = preferredHeight
468: .resolve(parentHeight);
469: }
470:
471: final long height = InfiniteMajorAxisLayoutStep
472: .computeHeight(minHeightResolved,
473: maxHeightResolved, prefHeightResolved);
474: return Math.min(height, usableParentHeight);
475: } else {
476: consumedHeight = Math.max(0, consumedHeight - insets);
477: final long minHeightResolved = minHeight
478: .resolve(parentHeight);
479: final long maxHeightResolved = maxHeight
480: .resolve(parentHeight);
481: final long prefHeightResolved;
482: if (preferredHeight == RenderLength.AUTO) {
483: prefHeightResolved = consumedHeight;
484: } else {
485: prefHeightResolved = preferredHeight
486: .resolve(parentHeight);
487: }
488:
489: final long height = InfiniteMajorAxisLayoutStep
490: .computeHeight(minHeightResolved,
491: maxHeightResolved, prefHeightResolved);
492: return Math.min(height + insets, usableParentHeight);
493: }
494: }
495:
496: }
|