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: * ComputeStaticPropertiesProcessStep.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.fonts.registry.BaselineInfo;
032: import org.jfree.fonts.registry.FontMetrics;
033: import org.jfree.report.layout.model.BlockRenderBox;
034: import org.jfree.report.layout.model.Border;
035: import org.jfree.report.layout.model.CanvasRenderBox;
036: import org.jfree.report.layout.model.FinishedRenderNode;
037: import org.jfree.report.layout.model.LogicalPageBox;
038: import org.jfree.report.layout.model.ParagraphRenderBox;
039: import org.jfree.report.layout.model.RenderBox;
040: import org.jfree.report.layout.model.RenderLength;
041: import org.jfree.report.layout.model.RenderNode;
042: import org.jfree.report.layout.model.RenderableReplacedContent;
043: import org.jfree.report.layout.model.RenderableText;
044: import org.jfree.report.layout.model.context.BoxDefinition;
045: import org.jfree.report.layout.model.context.StaticBoxLayoutProperties;
046: import org.jfree.report.layout.output.OutputProcessorMetaData;
047: import org.jfree.report.layout.text.ExtendedBaselineInfo;
048: import org.jfree.report.layout.text.TextUtility;
049: import org.jfree.report.style.BandStyleKeys;
050: import org.jfree.report.style.ElementStyleKeys;
051: import org.jfree.report.style.StyleSheet;
052: import org.jfree.report.style.TextStyleKeys;
053: import org.jfree.report.style.WhitespaceCollapse;
054: import org.jfree.report.util.geom.StrictGeomUtility;
055:
056: /**
057: * Computes the width for all elements. This uses the CSS alogorithm, percentages are resolved against the parent's
058: * already known width.
059: *
060: * @author Thomas Morgner
061: */
062: public final class ComputeStaticPropertiesProcessStep extends
063: IterateVisualProcessStep {
064: // Set the maximum height to an incredibly high value. This is now 2^43 micropoints or more than
065: // 3000 kilometers. Please call me directly at any time if you need more space for printing.
066: private static final long MAX_AUTO = StrictGeomUtility
067: .toInternalValue(0x80000000000L);
068:
069: private static final boolean ENABLE_ASSERTATION = false;
070: private LogicalPageBox logicalPage;
071: private OutputProcessorMetaData metaData;
072: private BaselineInfo baselineInfo;
073:
074: public ComputeStaticPropertiesProcessStep() {
075: this .baselineInfo = new BaselineInfo();
076: }
077:
078: public void compute(final LogicalPageBox root,
079: final OutputProcessorMetaData metaData) {
080: try {
081: this .logicalPage = root;
082: this .metaData = metaData;
083: startProcessing(root);
084: } finally {
085: this .logicalPage = null;
086: this .metaData = null;
087: }
088: // Log.debug ("Performance: " + cacheHit + ":" + cacheMiss + ":" + cacheFill);
089: }
090:
091: protected void processParagraphChilds(final ParagraphRenderBox box) {
092: // startProcessing(box.getLineboxContainer());
093: startProcessing(box.getPool());
094: }
095:
096: protected boolean startBlockLevelBox(final RenderBox box) {
097: final long age = box.getStaticBoxPropertiesAge();
098: final long changeTracker = box.getChangeTracker();
099:
100: if (age == changeTracker) {
101: // the node has been computed in the past, and there have been no structural changes later.
102: assertChildValid(box);
103: return false;
104: }
105: if (age > -1) {
106: // the node has been computed in the past, but new nodes have been added.
107: assertBoxValid(box);
108: return true;
109: }
110:
111: computeBreakIndicator(box);
112:
113: final long parentContextWidth = updateStaticProperties(box);
114: final BoxDefinition boxDefinition = box.getBoxDefinition();
115:
116: final RenderLength preferredWidth = boxDefinition
117: .getPreferredWidth();
118: final RenderLength minimumWidth = boxDefinition
119: .getMinimumWidth();
120: final RenderLength maximumWidth = boxDefinition
121: .getMaximumWidth();
122:
123: final long rprefW = preferredWidth.resolve(parentContextWidth,
124: parentContextWidth);
125: final long rminW = minimumWidth.resolve(parentContextWidth, 0);
126: final long rmaxW = maximumWidth.resolve(parentContextWidth,
127: MAX_AUTO);
128:
129: final long computedWidth = computeWidth(rminW, rmaxW, rprefW);
130: box.setComputedX(computeParentContentBoxX(box));
131:
132: if (boxDefinition.isSizeSpecifiesBorderBox() == false) {
133: final StaticBoxLayoutProperties sblp = box
134: .getStaticBoxLayoutProperties();
135: final long horizontalInsets = boxDefinition
136: .getPaddingLeft()
137: + boxDefinition.getPaddingRight()
138: + sblp.getBorderLeft() + sblp.getBorderRight();
139: box.setComputedWidth(Math.max(0, computedWidth
140: + horizontalInsets));
141: } else {
142: box.setComputedWidth(Math.max(0, computedWidth));
143: }
144: return true;
145: }
146:
147: private void assertBoxValid(final RenderBox box) {
148: if (ENABLE_ASSERTATION
149: && box.getStaticBoxLayoutProperties()
150: .getNominalBaselineInfo() == null) {
151: throw new NullPointerException();
152: }
153: }
154:
155: private void assertChildValid(final RenderBox box) {
156: if (ENABLE_ASSERTATION && box.getFirstChild() != null) {
157: final RenderNode node = box.getFirstChild();
158: if (node instanceof RenderBox) {
159: final RenderBox cbox = (RenderBox) node;
160: assertBoxValid(cbox);
161: }
162: }
163: }
164:
165: protected boolean startInlineLevelBox(final RenderBox box) {
166: final long age = box.getStaticBoxPropertiesAge();
167: final long changeTracker = box.getChangeTracker();
168: if (age == changeTracker) {
169: // the node has been computed in the past, and there were no structural changes later.
170: // cacheHit += 1;
171: assertChildValid(box);
172: return false;
173: }
174: if (age > -1) {
175: // the node has been computed in the past, but new nodes have been added.
176: // cacheMiss += 1;
177: assertBoxValid(box);
178: return true;
179: }
180:
181: // cacheFill += 1;
182:
183: updateStaticProperties(box);
184: box.setComputedWidth(0); // A inline level box can never have a computed width.
185: box.setComputedX(0); // The position of an inline box is not known yet and cannot be computed here
186: return true;
187: }
188:
189: protected boolean startCanvasLevelBox(final RenderBox box) {
190: final long age = box.getStaticBoxPropertiesAge();
191: final long changeTracker = box.getChangeTracker();
192: if (age == changeTracker) {
193: // the node has been computed in the past, and there were no structural changes later.
194: // cacheHit += 1;
195: return false;
196: }
197: if (age > -1) {
198: // the node has been computed in the past, but new nodes have been added.
199: // cacheMiss += 1;
200: return true;
201: }
202:
203: // cacheFill += 1;
204:
205: computeBreakIndicator(box);
206:
207: // For now, we silently assume that percentages in an canvas-element get resolved against the parent's width
208: // This should still result in valid behavior most of the time, while it greatly simplifies the layouting model.
209: final long contextWidth = updateStaticProperties(box);
210:
211: final BoxDefinition boxDefinition = box.getBoxDefinition();
212: final RenderLength preferredWidth = boxDefinition
213: .getPreferredWidth();
214: final RenderLength minimumWidth = boxDefinition
215: .getMinimumWidth();
216: final RenderLength maximumWidth = boxDefinition
217: .getMaximumWidth();
218:
219: // docmark: Changed the auto-mapping to zero. Bands should not expand infinitely ..
220: final long autoWidth = computeAutoWidth(box);
221: final long rprefW = preferredWidth.resolve(contextWidth,
222: autoWidth);
223: final long rminW = minimumWidth.resolve(contextWidth, 0);
224: final long rmaxW = maximumWidth.resolve(contextWidth, MAX_AUTO);
225: final long computedWidth = Math.max(0, computeWidth(rminW,
226: rmaxW, rprefW));
227:
228: final StyleSheet styleSheet = box.getStyleSheet();
229: final double posX = styleSheet.getDoubleStyleProperty(
230: ElementStyleKeys.POS_X, 0);
231: final long rx = RenderLength.resolveLength(contextWidth, posX);
232:
233: final StaticBoxLayoutProperties sblp = box
234: .getStaticBoxLayoutProperties();
235: final long insetsLeft = boxDefinition.getPaddingLeft()
236: + sblp.getBorderLeft();
237: final long insets = insetsLeft
238: + boxDefinition.getPaddingRight()
239: + sblp.getBorderRight();
240:
241: if (boxDefinition.isSizeSpecifiesBorderBox()) {
242: box.setComputedX(rx + computeParentContentBoxX(box));
243: if (rx > contextWidth) {
244: // This element will not be visible, it is outside of the page-area ..
245: box.setComputedWidth(0);
246: } else if ((rx + computedWidth) > contextWidth) {
247: // The element will be partially out of scope. It consumes as much as it can...
248: box.setComputedWidth(Math.max(0, contextWidth - rx));
249: } else {
250: box.setComputedWidth(Math.max(0, computedWidth));
251: }
252: } else {
253: // all sizes specify the content-box. Therefore, to compute the real x in the border-box we have to
254: // subtract the border and padding sizes. (The computed X can be negative now.)
255: final long realX = rx - insetsLeft;
256: box.setComputedX(realX + computeParentContentBoxX(box));
257: if (realX > contextWidth) {
258: // This element will not be visible, it is outside of the page-area ..
259: box.setComputedWidth(0);
260: } else if ((realX + computedWidth + insets) > contextWidth) {
261: // The element will be partially out of scope.
262: box.setComputedWidth(contextWidth - realX);
263: } else {
264: // the element fully fits into the parent.
265: box.setComputedWidth(computedWidth + insets);
266: }
267: }
268: return true;
269:
270: }
271:
272: /**
273: * This is a hack. In this basic layout model we normally do not resolve the AUTO-length against the intrinsic
274: * length of the paragraphs and replaced content. We should, but in the canvas model, we can run cheaper without
275: * it.
276: *
277: * @param box
278: * @return
279: */
280: private long computeAutoWidth(final RenderBox box) {
281: final RenderNode child = box.getFirstChild();
282: if (child instanceof RenderableReplacedContent) {
283: final RenderableReplacedContent rpc = (RenderableReplacedContent) child;
284: return rpc.getContentWidth();
285: }
286: return 0;
287: }
288:
289: protected void processBlockLevelNode(final RenderNode node) {
290: if (node instanceof FinishedRenderNode) {
291: final FinishedRenderNode fnode = (FinishedRenderNode) node;
292: node.setComputedWidth(fnode.getLayoutedWidth());
293: } else if (node instanceof RenderableReplacedContent) {
294: final long bcw = computeBlockContextWidth(node);
295: final RenderableReplacedContent prc = (RenderableReplacedContent) node;
296: node.setComputedWidth(prc.computeWidth(bcw));
297: } else {
298: final long bcw = computeBlockContextWidth(node);
299: node.setComputedWidth(bcw);
300: }
301: node.setComputedX(computeParentContentBoxX(node));
302: }
303:
304: private long computeParentContentBoxX(final RenderNode node) {
305: final RenderBox parent = node.getParent();
306: if (parent == null) {
307: return 0;
308: }
309: final BoxDefinition boxDefinition = parent.getBoxDefinition();
310: final StaticBoxLayoutProperties sblp = parent
311: .getStaticBoxLayoutProperties();
312: final long insets = boxDefinition.getPaddingLeft()
313: + sblp.getBorderLeft();
314: return parent.getComputedX() + insets;
315: }
316:
317: protected void processInlineLevelNode(final RenderNode node) {
318: // they have no computable size/position or something else ..
319: node.setComputedWidth(0);
320: node.setComputedX(0);
321: }
322:
323: protected void processCanvasLevelNode(final RenderNode node) {
324: if (node instanceof RenderableText) {
325: // If this happens, we really have a problem ..
326: throw new IllegalStateException(
327: "Encountered RenderableText outside of an inline-context.");
328: }
329:
330: if (node instanceof RenderableReplacedContent) {
331: final long contextWidth = computeBlockContextWidth(node);
332: final RenderableReplacedContent content = (RenderableReplacedContent) node;
333: final StyleSheet styleSheet = node.getStyleSheet();
334: final long computedWidth = content
335: .computeWidth(contextWidth);
336:
337: final double posX = styleSheet.getDoubleStyleProperty(
338: ElementStyleKeys.POS_X, 0);
339: // Here, we have no need to differentiate between border-box and content-box, as the
340: // replaced content never has a direct border
341: final long rx = RenderLength.resolveLength(contextWidth,
342: posX);
343: if (rx > contextWidth) {
344: node.setComputedWidth(0);
345: node.setComputedX(rx + computeParentContentBoxX(node));
346: } else if ((rx + computedWidth) > contextWidth) {
347: node.setComputedWidth(contextWidth - rx);
348: node.setComputedX(rx + computeParentContentBoxX(node));
349: } else {
350: node.setComputedWidth(computedWidth);
351: node.setComputedX(rx + computeParentContentBoxX(node));
352: }
353: }
354: }
355:
356: /**
357: * Returns the computed block-context width. This width is a content-size width - so it excludes paddings
358: * and borders. (See CSS3-BOX 4.2; http://www.w3.org/TR/css3-box/#containing)
359: *
360: * @param box the box for which the block-context width should be computed.
361: * @return the block context width.
362: */
363: private long computeBlockContextWidth(final RenderNode box) {
364: final RenderBox parentBlockContext = box.getParent();
365: if (parentBlockContext == null) {
366: // page cannot have borders ...
367: return logicalPage.getPageWidth();
368: }
369: if (parentBlockContext instanceof BlockRenderBox
370: || parentBlockContext instanceof CanvasRenderBox) {
371: final BoxDefinition boxDefinition = parentBlockContext
372: .getBoxDefinition();
373: final StaticBoxLayoutProperties sblp = parentBlockContext
374: .getStaticBoxLayoutProperties();
375: final long insets = sblp.getBorderLeft()
376: + sblp.getBorderRight()
377: + boxDefinition.getPaddingLeft()
378: + boxDefinition.getPaddingRight();
379: return Math.max(0, parentBlockContext.getComputedWidth()
380: - insets);
381: }
382: // The parent's computed width is used as block context ..
383: return parentBlockContext.getStaticBoxLayoutProperties()
384: .getBlockContextWidth();
385: }
386:
387: private void computeBreakIndicator(final RenderBox box) {
388: final StyleSheet styleSheet = box.getStyleSheet();
389: final boolean breakBefore = styleSheet
390: .getBooleanStyleProperty(BandStyleKeys.PAGEBREAK_BEFORE);
391: final boolean breakAfter = box.isBreakAfter();
392: final RenderBox parent = box.getParent();
393: final boolean fixedPosition = RenderLength.AUTO
394: .equals(styleSheet
395: .getStyleProperty(BandStyleKeys.FIXED_POSITION,
396: RenderLength.AUTO)) == false;
397: if ((breakBefore)
398: && (parent instanceof ParagraphRenderBox == false)) {
399: box.setManualBreakIndicator(RenderBox.DIRECT_MANUAL_BREAK);
400: applyIndirectManualBreakIndicator(parent);
401: return;
402: }
403: if (breakAfter
404: && (parent instanceof ParagraphRenderBox == false)) {
405: if (parent != null) {
406: applyIndirectManualBreakIndicator(parent);
407: }
408: }
409: if (fixedPosition) {
410: applyIndirectManualBreakIndicator(box);
411: } else {
412: box.setManualBreakIndicator(RenderBox.NO_MANUAL_BREAK);
413: }
414: }
415:
416: private void applyIndirectManualBreakIndicator(RenderBox node) {
417: while (node != null) {
418: if (node.getManualBreakIndicator() != RenderBox.NO_MANUAL_BREAK) {
419: return;
420: }
421: node
422: .setManualBreakIndicator(RenderBox.INDIRECT_MANUAL_BREAK);
423: node = node.getParent();
424: }
425: }
426:
427: /**
428: * Collects and possibly computes the static properties according to the CSS layouting model. The classic JFreeReport
429: * layout model does not know anything about margins or borders, so in that case resolving against the CSS model is
430: * ok.
431: *
432: * @param box
433: * @return the computed width of the containing block (the so-called block-context width).
434: */
435: private long updateStaticProperties(final RenderBox box) {
436: final BoxDefinition boxDefinition = box.getBoxDefinition();
437: final StaticBoxLayoutProperties sblp = box
438: .getStaticBoxLayoutProperties();
439: if (sblp.getNominalBaselineInfo() == null) {
440: final long parentWidth = computeBlockContextWidth(box);
441: sblp.setMarginTop(boxDefinition.getMarginTop().resolve(
442: parentWidth));
443: sblp.setMarginLeft(boxDefinition.getMarginLeft().resolve(
444: parentWidth));
445: sblp.setMarginBottom(boxDefinition.getMarginBottom()
446: .resolve(parentWidth));
447: sblp.setMarginRight(boxDefinition.getMarginRight().resolve(
448: parentWidth));
449:
450: final Border border = boxDefinition.getBorder();
451: sblp.setBorderTop(border.getTop().getWidth());
452: sblp.setBorderLeft(border.getLeft().getWidth());
453: sblp.setBorderBottom(border.getBottom().getWidth());
454: sblp.setBorderRight(border.getRight().getWidth());
455:
456: final StyleSheet style = box.getStyleSheet();
457: sblp.setAvoidPagebreakInside(style.getBooleanStyleProperty(
458: ElementStyleKeys.AVOID_PAGEBREAK_INSIDE, false));
459: sblp.setDominantBaseline(-1);
460: sblp.setOrphans(style.getIntStyleProperty(
461: ElementStyleKeys.ORPHANS, 0));
462: sblp.setWidows(style.getIntStyleProperty(
463: ElementStyleKeys.WIDOWS, 0));
464:
465: final FontMetrics fontMetrics = metaData
466: .getFontMetrics(style);
467: final ExtendedBaselineInfo baselineInfo = TextUtility
468: .createBaselineInfo('x', fontMetrics,
469: this .baselineInfo);
470: if (baselineInfo == null) {
471: throw new IllegalStateException();
472: }
473: sblp.setNominalBaselineInfo(baselineInfo);
474: sblp.setFontFamily(metaData
475: .getNormalizedFontFamilyName((String) style
476: .getStyleProperty(TextStyleKeys.FONT)));
477: final Object collapse = style
478: .getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE);
479: sblp.setPreserveSpace(WhitespaceCollapse.PRESERVE
480: .equals(collapse));
481:
482: sblp.setBlockContextWidth(parentWidth);
483: return parentWidth;
484: } else {
485: // Log.debug ("Compute box: " + box + " is not needed.");
486: // throw new IllegalStateException("Tried to compute the nominal baseline info twice.");
487: return sblp.getBlockContextWidth();
488: }
489: }
490:
491: private long computeWidth(final long min, final long max,
492: final long pref) {
493: if (pref > max) {
494: if (max < min) {
495: return min;
496: }
497: return max;
498: }
499:
500: if (pref < min) {
501: if (max < min) {
502: return max;
503: }
504: return min;
505: }
506:
507: if (max < pref) {
508: return max;
509: }
510: return pref;
511: }
512:
513: }
|