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: * InfiniteMinorAxisLayoutStep.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.style.StyleSheet;
033: import org.jfree.report.style.TextStyleKeys;
034: import org.jfree.report.style.WhitespaceCollapse;
035: import org.jfree.report.layout.model.FinishedRenderNode;
036: import org.jfree.report.layout.model.InlineRenderBox;
037: import org.jfree.report.layout.model.LogicalPageBox;
038: import org.jfree.report.layout.model.PageGrid;
039: import org.jfree.report.layout.model.ParagraphPoolBox;
040: import org.jfree.report.layout.model.ParagraphRenderBox;
041: import org.jfree.report.layout.model.RenderBox;
042: import org.jfree.report.layout.model.RenderNode;
043: import org.jfree.report.layout.model.RenderableReplacedContent;
044: import org.jfree.report.layout.model.RenderableText;
045: import org.jfree.report.layout.model.SpacerRenderNode;
046: import org.jfree.report.layout.model.context.BoxDefinition;
047: import org.jfree.report.layout.model.context.StaticBoxLayoutProperties;
048: import org.jfree.report.layout.process.alignment.CenterAlignmentProcessor;
049: import org.jfree.report.layout.process.alignment.LeftAlignmentProcessor;
050: import org.jfree.report.layout.process.alignment.RightAlignmentProcessor;
051: import org.jfree.report.layout.process.alignment.TextAlignmentProcessor;
052: import org.jfree.report.layout.process.layoutrules.EndSequenceElement;
053: import org.jfree.report.layout.process.layoutrules.InlineBoxSequenceElement;
054: import org.jfree.report.layout.process.layoutrules.InlineNodeSequenceElement;
055: import org.jfree.report.layout.process.layoutrules.ReplacedContentSequenceElement;
056: import org.jfree.report.layout.process.layoutrules.SequenceList;
057: import org.jfree.report.layout.process.layoutrules.SpacerSequenceElement;
058: import org.jfree.report.layout.process.layoutrules.StartSequenceElement;
059: import org.jfree.report.layout.process.layoutrules.TextSequenceElement;
060: import org.jfree.util.Log;
061:
062: /**
063: * This process-step computes the effective layout, but it does not take horizontal pagebreaks into account. (It has to
064: * deal with vertical breaks, as they affect the text layout.)
065: * <p/>
066: * This processing step does not ajust anything on the vertical axis. Vertical alignment is handled in a second step.
067: * <p/>
068: * Please note: This layout model (unlike the default CSS model) uses the BOX-WIDTH as computed with. This means, the
069: * defined width specifies the sum of all borders, paddings and the content area width.
070: *
071: * @author Thomas Morgner
072: */
073: public final class InfiniteMinorAxisLayoutStep extends
074: IterateVisualProcessStep {
075:
076: private MinorAxisParagraphBreakState breakState;
077: private PageGrid pageGrid;
078: private RenderBox continuedElement;
079:
080: private TextAlignmentProcessor centerProcessor;
081: private TextAlignmentProcessor rightProcessor;
082: private TextAlignmentProcessor leftProcessor;
083:
084: public InfiniteMinorAxisLayoutStep() {
085: breakState = new MinorAxisParagraphBreakState();
086: }
087:
088: public void compute(final LogicalPageBox root) {
089: try {
090: continuedElement = null;
091: pageGrid = root.getPageGrid();
092: startProcessing(root);
093: } finally {
094: continuedElement = null;
095: pageGrid = null;
096: breakState.deinit();
097: }
098: }
099:
100: /**
101: * Continues processing. The renderbox must have a valid x-layout (that is: X, content-X1, content-X2 and Width)
102: *
103: * @param pageGrid
104: * @param box
105: */
106: public void continueComputation(final PageGrid pageGrid,
107: final RenderBox box) {
108: if (box.getContentAreaX2() == 0 || box.getCachedWidth() == 0) {
109: throw new IllegalStateException(
110: "Box must be layouted a bit ..");
111: }
112:
113: try {
114: this .pageGrid = pageGrid;
115: this .breakState.deinit();
116: this .continuedElement = box;
117: startProcessing(box);
118: } finally {
119: this .continuedElement = null;
120: this .pageGrid = null;
121: this .breakState.deinit();
122: }
123: }
124:
125: /**
126: * The whole computation is only done for exactly one nesting level of paragraphs. If we encounter an inline-block or
127: * inline-table, we handle them as a single element.
128: *
129: * @param box
130: * @return
131: */
132: protected boolean startBlockLevelBox(final RenderBox box) {
133: // first, compute the position. The position is global, not relative to a
134: // parent or so. Therefore a child has no connection to the parent's
135: // effective position, when it is painted.
136:
137: if (breakState.isActive() == false) {
138: if (box.isCacheValid()) {
139: return false;
140: }
141:
142: computeContentArea(box);
143:
144: if (box instanceof ParagraphRenderBox) {
145:
146: final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
147:
148: if (continuedElement == null) {
149: final long lineBoxChangeTracker;
150: if (paragraphBox.isComplexParagraph()) {
151: lineBoxChangeTracker = paragraphBox
152: .getLineboxContainer()
153: .getChangeTracker();
154: } else {
155: lineBoxChangeTracker = paragraphBox.getPool()
156: .getChangeTracker();
157: }
158:
159: final boolean unchanged = lineBoxChangeTracker == paragraphBox
160: .getMinorLayoutAge();
161: if (unchanged) {
162: return false;
163: }
164: }
165:
166: paragraphBox.clearLayout();
167: breakState.init(paragraphBox);
168: }
169: return true;
170: }
171:
172: if (breakState.isSuspended() == false) {
173: // The break-state exists only while we are inside of an paragraph
174: // and suspend can only happen on inline elements.
175: // A block-element inside a paragraph cannot be (and if it does, it is
176: // a bug)
177: throw new IllegalStateException("This cannot be.");
178: }
179:
180: // this way or another - we are suspended now. So there is no need to look
181: // at the children anymore ..
182: return false;
183: }
184:
185: protected void finishBlockLevelBox(final RenderBox box) {
186: // Todo: maybe it would be very wise if we dont extend the box later on.
187: // Heck, lets see whether this causes great pain ...
188: verifyContentWidth(box);
189:
190: if (breakState.isActive()) {
191: final Object suspender = breakState.getSuspendItem();
192: if (box.getInstanceId() == suspender) {
193: breakState.setSuspendItem(null);
194: return;
195: }
196: if (suspender != null) {
197: return;
198: }
199:
200: if (box instanceof ParagraphRenderBox) {
201: // finally update the change tracker ..
202: final ParagraphRenderBox paraBox = (ParagraphRenderBox) box;
203: if (paraBox.isComplexParagraph()) {
204: paraBox.setMinorLayoutAge(paraBox
205: .getLineboxContainer().getChangeTracker());
206: } else {
207: paraBox.setMinorLayoutAge(paraBox.getPool()
208: .getChangeTracker());
209: }
210:
211: breakState.deinit();
212: }
213: }
214:
215: }
216:
217: /**
218: * Computes the effective content area. The content area is the space that can be used by any of the childs of the
219: * given box.
220: * <p/>
221: * InlineBoxes get computed in the alignment processor.
222: *
223: * @param box the block render box for which we compute the content area
224: */
225: private void computeContentArea(final RenderBox box) {
226: if (box == continuedElement) {
227: return;
228: }
229:
230: final BoxDefinition bdef = box.getBoxDefinition();
231: final StaticBoxLayoutProperties blp = box
232: .getStaticBoxLayoutProperties();
233: final long x = box.getComputedX();
234: box.setCachedX(x);
235: // next, compute the width ...
236:
237: final long leftPadding = blp.getBorderLeft()
238: + bdef.getPaddingLeft();
239: final long rightPadding = blp.getBorderRight()
240: + bdef.getPaddingRight();
241: box.setContentAreaX1(x + leftPadding);
242: box.setContentAreaX2(x + box.getComputedWidth() - rightPadding);
243: }
244:
245: /**
246: * Verifies the content width and produces the effective box width. (is called from finishBLock and finishCanvas)
247: *
248: * @param box
249: */
250: private void verifyContentWidth(final RenderBox box) {
251: final long x = box.getCachedX();
252: final long contentEnd = box.getContentAreaX2();
253: final StaticBoxLayoutProperties blp = box
254: .getStaticBoxLayoutProperties();
255: final BoxDefinition bdef = box.getBoxDefinition();
256: final long boxEnd = contentEnd + blp.getBorderRight()
257: + bdef.getPaddingRight();
258: box.setCachedWidth(boxEnd - x);
259: }
260:
261: protected boolean startInlineLevelBox(final RenderBox box) {
262: if (breakState.isActive() == false) {
263: // ignore .. should not happen anyway ..
264: if (box.isCacheValid()) {
265: return false;
266: }
267: return true;
268: }
269:
270: if (breakState.isSuspended()) {
271: return false;
272: }
273:
274: if (box instanceof InlineRenderBox) {
275: breakState.add(StartSequenceElement.INSTANCE, box);
276: return true;
277: }
278:
279: computeContentArea(box);
280:
281: breakState.add(InlineBoxSequenceElement.INSTANCE, box);
282: breakState.setSuspendItem(box.getInstanceId());
283: return false;
284: }
285:
286: protected void finishInlineLevelBox(final RenderBox box) {
287: if (breakState.isActive() == false) {
288: return;
289: }
290: if (breakState.getSuspendItem() == box.getInstanceId()) {
291: // stop being suspended.
292: breakState.setSuspendItem(null);
293: return;
294: }
295:
296: if (box instanceof InlineRenderBox) {
297: breakState.add(EndSequenceElement.INSTANCE, box);
298: return;
299: }
300:
301: final Object suspender = breakState.getSuspendItem();
302: if (box.getInstanceId() == suspender) {
303: breakState.setSuspendItem(null);
304: return;
305: }
306:
307: if (suspender != null) {
308: return;
309: }
310:
311: if (box instanceof ParagraphRenderBox) {
312: throw new IllegalStateException("This cannot be.");
313: }
314:
315: }
316:
317: protected void processInlineLevelNode(final RenderNode node) {
318: if (breakState.isActive() == false || breakState.isSuspended()) {
319: return;
320: }
321:
322: if (node instanceof FinishedRenderNode) {
323: final FinishedRenderNode finNode = (FinishedRenderNode) node;
324: node.setCachedWidth(finNode.getLayoutedWidth());
325: }
326:
327: if (node instanceof RenderableText) {
328: breakState.add(TextSequenceElement.INSTANCE, node);
329: } else if (node instanceof RenderableReplacedContent) {
330: breakState.add(ReplacedContentSequenceElement.INSTANCE,
331: node);
332: } else if (node instanceof SpacerRenderNode) {
333: final StyleSheet styleSheet = node.getStyleSheet();
334: if (WhitespaceCollapse.PRESERVE
335: .equals(styleSheet
336: .getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE))
337: && styleSheet
338: .getBooleanStyleProperty(TextStyleKeys.TRIM_TEXT_CONTENT) == false) {
339: breakState.add(SpacerSequenceElement.INSTANCE, node);
340: } else if (breakState.isContainsContent()) {
341: breakState.add(SpacerSequenceElement.INSTANCE, node);
342: }
343: } else {
344: breakState.add(InlineNodeSequenceElement.INSTANCE, node);
345: }
346: }
347:
348: protected void processBlockLevelNode(final RenderNode node) {
349: // This could be anything, text, or an image.
350: node.setCachedX(node.getComputedX());
351: if (node instanceof FinishedRenderNode) {
352: final FinishedRenderNode finNode = (FinishedRenderNode) node;
353: node.setCachedWidth(finNode.getLayoutedWidth());
354: } else {
355: node.setCachedWidth(node.getComputedWidth());
356: }
357: }
358:
359: protected void processCanvasLevelNode(final RenderNode node) {
360: // next, compute the width ...
361: node.setCachedX(node.getComputedX());
362: if (node instanceof FinishedRenderNode) {
363: final FinishedRenderNode finNode = (FinishedRenderNode) node;
364: node.setCachedWidth(finNode.getLayoutedWidth());
365: } else {
366: node.setCachedWidth(node.getComputedWidth());
367: }
368: }
369:
370: protected void processParagraphChilds(final ParagraphRenderBox box) {
371: if (box.isComplexParagraph()) {
372: final RenderBox lineboxContainer = box
373: .getLineboxContainer();
374: RenderNode node = lineboxContainer.getVisibleFirst();
375: while (node != null) {
376: // all childs of the linebox container must be inline boxes. They
377: // represent the lines in the paragraph. Any other element here is
378: // a error that must be reported
379: if (node instanceof ParagraphPoolBox == false) {
380: throw new IllegalStateException(
381: "Expected ParagraphPoolBox elements.");
382: }
383:
384: final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
385: if (startLine(inlineRenderBox)) {
386: processBoxChilds(inlineRenderBox);
387: finishLine(inlineRenderBox);
388: }
389:
390: node = node.getVisibleNext();
391: }
392: } else {
393: final ParagraphPoolBox node = box.getPool();
394: // all childs of the linebox container must be inline boxes. They
395: // represent the lines in the paragraph. Any other element here is
396: // a error that must be reported
397: if (startLine(node)) {
398: processBoxChilds(node);
399: finishLine(node);
400: }
401: }
402: }
403:
404: private boolean startLine(final ParagraphPoolBox inlineRenderBox) {
405: if (breakState.isActive() == false) {
406: return false;
407: }
408:
409: if (breakState.isSuspended()) {
410: return false;
411: }
412:
413: breakState.clear();
414: breakState.add(StartSequenceElement.INSTANCE, inlineRenderBox);
415: return true;
416: }
417:
418: private void finishLine(final ParagraphPoolBox inlineRenderBox) {
419: if (breakState.isActive() == false || breakState.isSuspended()) {
420: throw new IllegalStateException(
421: "No active breakstate, finish-line cannot continue.");
422: }
423:
424: breakState.add(EndSequenceElement.INSTANCE, inlineRenderBox);
425:
426: final ParagraphRenderBox paragraph = breakState.getParagraph();
427:
428: final ElementAlignment textAlignment = paragraph
429: .getTextAlignment();
430:
431: // This aligns all direct childs. Once that is finished, we have to
432: // check, whether possibly existing inner-paragraphs are still valid
433: // or whether moving them violated any of the inner-pagebreak constraints.
434: final TextAlignmentProcessor processor = create(textAlignment);
435:
436: final SequenceList sequence = breakState.getSequence();
437:
438: final long lineStart = paragraph.getContentAreaX1();
439: final long lineEnd = paragraph.getContentAreaX2();
440: if (lineEnd - lineStart <= 0) {
441: final long minimumChunkWidth = paragraph
442: .getMinimumChunkWidth();
443: processor.initialize(sequence, lineStart, lineStart
444: + minimumChunkWidth, pageGrid);
445: Log.warn("Auto-Corrected zero-width linebox on "
446: + paragraph.getName());
447: } else {
448: processor
449: .initialize(sequence, lineStart, lineEnd, pageGrid);
450: }
451:
452: while (processor.hasNext()) {
453: final RenderNode linebox = processor.next();
454: if (linebox instanceof ParagraphPoolBox == false) {
455: throw new NullPointerException("Line must not be null");
456: }
457:
458: paragraph.addGeneratedChild(linebox);
459: }
460:
461: processor.deinitialize();
462: }
463:
464: /**
465: * Reuse the processors ..
466: *
467: * @param alignment
468: * @return
469: */
470: private TextAlignmentProcessor create(
471: final ElementAlignment alignment) {
472: if (ElementAlignment.CENTER.equals(alignment)) {
473: if (centerProcessor == null) {
474: centerProcessor = new CenterAlignmentProcessor();
475: }
476: return centerProcessor;
477: } else if (ElementAlignment.RIGHT.equals(alignment)) {
478: if (rightProcessor == null) {
479: rightProcessor = new RightAlignmentProcessor();
480: }
481: return rightProcessor;
482: }
483:
484: if (leftProcessor == null) {
485: leftProcessor = new LeftAlignmentProcessor();
486: }
487: return leftProcessor;
488: }
489:
490: protected boolean startCanvasLevelBox(final RenderBox box) {
491: // first, compute the position. The position is global, not relative to a
492: // parent or so. Therefore a child has no connection to the parent's
493: // effective position, when it is painted.
494:
495: if (breakState.isActive() == false) {
496: if (box.isCacheValid()) {
497: return false;
498: }
499:
500: if (box != continuedElement) {
501: final StaticBoxLayoutProperties blp = box
502: .getStaticBoxLayoutProperties();
503: final long x = box.getComputedX();// + blp.getPositionX(); //todo
504: box.setCachedX(x);
505: // next, compute the width ...
506:
507: final BoxDefinition bdef = box.getBoxDefinition();
508: final long leftPadding = blp.getBorderLeft()
509: + bdef.getPaddingLeft();
510: final long rightPadding = blp.getBorderRight()
511: + bdef.getPaddingRight();
512: box.setContentAreaX1(x + leftPadding);
513: box.setContentAreaX2(x + box.getComputedWidth()
514: - rightPadding);
515: }
516:
517: if (box instanceof ParagraphRenderBox) {
518: final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
519: if (continuedElement == null) {
520: final long lineBoxChangeTracker;
521: if (paragraphBox.isComplexParagraph()) {
522: lineBoxChangeTracker = paragraphBox
523: .getLineboxContainer()
524: .getChangeTracker();
525: } else {
526: lineBoxChangeTracker = paragraphBox.getPool()
527: .getChangeTracker();
528: }
529:
530: final boolean unchanged = lineBoxChangeTracker == paragraphBox
531: .getMinorLayoutAge();
532: if (unchanged) {
533: return false;
534: }
535: }
536:
537: paragraphBox.clearLayout();
538: breakState.init(paragraphBox);
539: }
540: return true;
541: }
542:
543: if (breakState.isSuspended() == false) {
544: // The break-state exists only while we are inside of an paragraph
545: // and suspend can only happen on inline elements.
546: // A block-element inside a paragraph cannot be (and if it does, it is
547: // a bug)
548: throw new IllegalStateException("This cannot be.");
549: }
550:
551: // this way or another - we are suspended now. So there is no need to look
552: // at the children anymore ..
553: return false;
554: }
555:
556: protected void finishCanvasLevelBox(final RenderBox box) {
557: // Todo: maybe it would be very wise if we dont extend the box later on.
558: // Heck, lets see whether this causes great pain ...
559: verifyContentWidth(box);
560:
561: if (breakState.isActive()) {
562: final Object suspender = breakState.getSuspendItem();
563: if (box.getInstanceId() == suspender) {
564: breakState.setSuspendItem(null);
565: return;
566: }
567: if (suspender != null) {
568: return;
569: }
570:
571: if (box instanceof ParagraphRenderBox) {
572: // finally update the change tracker ..
573: final ParagraphRenderBox paraBox = (ParagraphRenderBox) box;
574: if (paraBox.isComplexParagraph()) {
575: paraBox.setMinorLayoutAge(paraBox
576: .getLineboxContainer().getChangeTracker());
577: } else {
578: paraBox.setMinorLayoutAge(paraBox.getPool()
579: .getChangeTracker());
580: }
581:
582: breakState.deinit();
583: }
584: }
585: }
586: }
|