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: * RevalidateAllAxisLayoutStep.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.FontMetrics;
032: import org.jfree.report.ElementAlignment;
033: import org.jfree.report.layout.model.BlockRenderBox;
034: import org.jfree.report.layout.model.InlineRenderBox;
035: import org.jfree.report.layout.model.LogicalPageBox;
036: import org.jfree.report.layout.model.PageGrid;
037: import org.jfree.report.layout.model.ParagraphPoolBox;
038: import org.jfree.report.layout.model.ParagraphRenderBox;
039: import org.jfree.report.layout.model.RenderBox;
040: import org.jfree.report.layout.model.RenderNode;
041: import org.jfree.report.layout.model.RenderableReplacedContent;
042: import org.jfree.report.layout.model.RenderableText;
043: import org.jfree.report.layout.model.SpacerRenderNode;
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.process.alignment.CenterAlignmentProcessor;
048: import org.jfree.report.layout.process.alignment.LastLineTextAlignmentProcessor;
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.layoutrules.EndSequenceElement;
052: import org.jfree.report.layout.process.layoutrules.InlineBoxSequenceElement;
053: import org.jfree.report.layout.process.layoutrules.InlineNodeSequenceElement;
054: import org.jfree.report.layout.process.layoutrules.ReplacedContentSequenceElement;
055: import org.jfree.report.layout.process.layoutrules.SequenceList;
056: import org.jfree.report.layout.process.layoutrules.SpacerSequenceElement;
057: import org.jfree.report.layout.process.layoutrules.StartSequenceElement;
058: import org.jfree.report.layout.process.layoutrules.TextSequenceElement;
059: import org.jfree.report.layout.process.valign.BoxAlignContext;
060: import org.jfree.report.layout.process.valign.InlineBlockAlignContext;
061: import org.jfree.report.layout.process.valign.NodeAlignContext;
062: import org.jfree.report.layout.process.valign.ReplacedContentAlignContext;
063: import org.jfree.report.layout.process.valign.TextElementAlignContext;
064: import org.jfree.report.layout.process.valign.VerticalAlignmentProcessor;
065: import org.jfree.report.style.ElementStyleKeys;
066: import org.jfree.report.util.geom.StrictGeomUtility;
067: import org.jfree.util.FastStack;
068: import org.jfree.util.Log;
069:
070: /**
071: * This final processing step revalidates the text-layouting and the vertical alignment of block-level elements.
072: *
073: * At this point, the layout is almost finished, but non-dynamic text elements may contain more content on the
074: * last line than actually needed. This step recomputes the vertical alignment and merges all extra lines into the
075: * last line.
076: *
077: * @author Thomas Morgner
078: */
079: public final class RevalidateAllAxisLayoutStep extends
080: IterateVisualProcessStep {
081: private static class MergeContext {
082: private RenderBox readContext;
083: private RenderBox writeContext;
084:
085: protected MergeContext(final RenderBox writeContext,
086: final RenderBox readContext) {
087: this .readContext = readContext;
088: this .writeContext = writeContext;
089: }
090:
091: public RenderBox getReadContext() {
092: return readContext;
093: }
094:
095: public RenderBox getWriteContext() {
096: return writeContext;
097: }
098: }
099:
100: // Set the maximum height to an incredibly high value. This is now 2^43 micropoints or more than
101: // 3000 kilometers. Please call me directly at any time if you need more space for printing.
102: private static final long MAX_AUTO = StrictGeomUtility
103: .toInternalValue(0x80000000000L);
104:
105: private RenderBox continuedElement;
106: private LastLineTextAlignmentProcessor centerProcessor;
107: private LastLineTextAlignmentProcessor leftProcessor;
108: private LastLineTextAlignmentProcessor rightProcessor;
109: private PageGrid pageGrid;
110: private OutputProcessorMetaData metaData;
111: private VerticalAlignmentProcessor verticalAlignmentProcessor;
112:
113: public RevalidateAllAxisLayoutStep() {
114: verticalAlignmentProcessor = new VerticalAlignmentProcessor();
115: }
116:
117: public void compute(final LogicalPageBox pageBox,
118: final OutputProcessorMetaData metaData) {
119: this .metaData = metaData;
120: this .continuedElement = null;
121: this .pageGrid = pageBox.getPageGrid();
122: try {
123: startProcessing(pageBox);
124: } finally {
125: this .continuedElement = null;
126: this .pageGrid = null;
127: this .metaData = null;
128: }
129: }
130:
131: protected boolean startBlockLevelBox(final RenderBox box) {
132: if (box.isIgnorableForRendering()) {
133: return false;
134: }
135:
136: if (box.isCacheValid()) {
137: return false;
138: }
139: return true;
140: }
141:
142: protected boolean startCanvasLevelBox(final RenderBox box) {
143: if (box.isIgnorableForRendering()) {
144: return false;
145: }
146:
147: if (box.isCacheValid()) {
148: return false;
149: }
150: return true;
151: }
152:
153: private void performVerticalBlockAlignment(
154: final ParagraphRenderBox box) {
155:
156: final RenderNode lastChildNode = box.getLastChild();
157:
158: if (lastChildNode == null) {
159: return;
160: }
161:
162: final BoxDefinition boxDefinition = box.getBoxDefinition();
163: final StaticBoxLayoutProperties blp = box
164: .getStaticBoxLayoutProperties();
165: final long insetBottom = blp.getBorderBottom()
166: + boxDefinition.getPaddingBottom();
167: final long insetTop = blp.getBorderTop()
168: + boxDefinition.getPaddingTop();
169:
170: final long childY2 = lastChildNode.getCachedY()
171: + lastChildNode.getCachedHeight()
172: + lastChildNode.getEffectiveMarginBottom();
173: final long childY1 = box.getFirstChild().getCachedY();
174: final long usedHeight = (childY2 - childY1);
175:
176: final long computedHeight = box.getCachedHeight();
177: if (computedHeight > usedHeight) {
178: // we have extra space to distribute. So lets shift some boxes.
179: final ElementAlignment valign = box
180: .getNodeLayoutProperties().getVerticalAlignment();
181: if (ElementAlignment.BOTTOM.equals(valign)) {
182: final long boxBottom = (box.getCachedY()
183: + box.getCachedHeight() - insetBottom);
184: final long delta = boxBottom - childY2;
185: CacheBoxShifter.shiftBoxChilds(box, delta);
186: } else if (ElementAlignment.MIDDLE.equals(valign)) {
187: final long extraHeight = computedHeight - usedHeight;
188: final long boxTop = box.getCachedY() + insetTop
189: + (extraHeight / 2);
190: final long delta = boxTop - childY1;
191: CacheBoxShifter.shiftBoxChilds(box, delta);
192: }
193: }
194: }
195:
196: protected void processParagraphChilds(
197: final ParagraphRenderBox paragraph) {
198: if (paragraph.getStyleSheet().getBooleanStyleProperty(
199: ElementStyleKeys.OVERFLOW_Y) == true) {
200: return;
201: }
202:
203: // Process the direct childs of the paragraph
204: // Each direct child represents a line ..
205: final long paragraphBottom = paragraph.getCachedY()
206: + paragraph.getCachedHeight();
207:
208: final RenderNode lastLine = paragraph.getVisibleLast();
209: if (lastLine == null) {
210: // Empty paragraph, no need to do anything ...
211: return;
212: }
213:
214: if ((lastLine.getCachedY() + lastLine.getCachedHeight()) <= paragraphBottom) {
215: // Already perfectly aligned.
216: return;
217: }
218:
219: RenderNode node = paragraph.getVisibleFirst();
220: ParagraphPoolBox prev = null;
221: while (node != null) {
222: // all childs of the linebox container must be inline boxes. They
223: // represent the lines in the paragraph. Any other element here is
224: // a error that must be reported
225: if (node instanceof ParagraphPoolBox == false) {
226: throw new IllegalStateException("Encountered "
227: + node.getClass());
228: }
229:
230: final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
231: // Process the current line.
232: final long y = inlineRenderBox.getCachedY();
233: final long height = inlineRenderBox.getCachedHeight();
234: if (y + height <= paragraphBottom) {
235:
236: // Node will fit, so we can allow it ..
237: prev = inlineRenderBox;
238: node = node.getVisibleNext();
239: continue;
240: }
241:
242: // Encountered a paragraph that will not fully fit into the paragraph.
243: // Merge it with the previous line-paragraph.
244: final ParagraphPoolBox mergedLine = rebuildLastLine(prev,
245: inlineRenderBox);
246:
247: // now remove all pending lineboxes (they should be empty anyway).
248: while (node != null) {
249: final RenderNode oldNode = node;
250: node = node.getVisibleNext();
251: paragraph.remove(oldNode);
252: }
253:
254: if (mergedLine == null) {
255: return;
256: }
257:
258: final ElementAlignment textAlignment = paragraph
259: .getLastLineAlignment();
260: final LastLineTextAlignmentProcessor proc = create(textAlignment);
261:
262: // Now Build the sequence list that holds all nodes for the horizontal alignment computation.
263: // The last line will get a special "last-line" horizontal alignment. This is quite usefull if
264: // we are working with justified text and want the last line to be left-aligned.
265: final SequenceList sequenceList = createHorizontalSequenceList(mergedLine);
266: final long lineStart = paragraph.getContentAreaX1();
267: final long lineEnd = paragraph.getContentAreaX2();
268: if (lineEnd - lineStart <= 0) {
269: final long minimumChunkWidth = paragraph
270: .getMinimumChunkWidth();
271: proc.initialize(sequenceList, lineStart, lineStart
272: + minimumChunkWidth, pageGrid);
273: Log.warn("Auto-Corrected zero-width linebox.");
274: } else {
275: proc.initialize(sequenceList, lineStart, lineEnd,
276: pageGrid);
277: }
278: proc.performLastLineAlignment();
279: proc.deinitialize();
280:
281: // Now Perform the vertical layout for the last line of the paragraph.
282: final BoxAlignContext valignContext = createVerticalAlignContext(mergedLine);
283: final StaticBoxLayoutProperties blp = mergedLine
284: .getStaticBoxLayoutProperties();
285: final BoxDefinition bdef = mergedLine.getBoxDefinition();
286: final long insetTop = (blp.getBorderTop() + bdef
287: .getPaddingTop());
288:
289: final long contentAreaY1 = mergedLine.getCachedY()
290: + insetTop;
291: final long lineHeight = mergedLine.getLineHeight();
292: verticalAlignmentProcessor.align(valignContext,
293: contentAreaY1, lineHeight);
294:
295: // And finally make sure that the paragraph box itself obeys to the defined vertical box alignment.
296: performVerticalBlockAlignment(paragraph);
297: return;
298: }
299: }
300:
301: private BoxAlignContext createVerticalAlignContext(
302: final InlineRenderBox box) {
303: BoxAlignContext alignContext = new BoxAlignContext(box);
304: final FastStack contextStack = new FastStack();
305: final FastStack alignContextStack = new FastStack();
306: RenderNode next = box.getFirstChild();
307: RenderBox context = box;
308:
309: while (next != null) {
310: // process next
311: if (next instanceof InlineRenderBox) {
312: final RenderBox nBox = (RenderBox) next;
313: final RenderNode firstChild = nBox.getFirstChild();
314: if (firstChild != null) {
315: // Open a non-empty box context
316: contextStack.push(context);
317: alignContextStack.push(alignContext);
318:
319: next = firstChild;
320:
321: final BoxAlignContext childBoxContext = new BoxAlignContext(
322: nBox);
323: alignContext.addChild(childBoxContext);
324: context = nBox;
325: alignContext = childBoxContext;
326: } else {
327: // Process an empty box.
328: final BoxAlignContext childBoxContext = new BoxAlignContext(
329: nBox);
330: alignContext.addChild(childBoxContext);
331: next = nBox.getNext();
332: }
333: } else {
334: // Process an ordinary node.
335: if (next instanceof RenderableText) {
336: alignContext.addChild(new TextElementAlignContext(
337: (RenderableText) next));
338: } else if (next instanceof RenderableReplacedContent) {
339: alignContext
340: .addChild(new ReplacedContentAlignContext(
341: (RenderableReplacedContent) next));
342: } else if (next instanceof BlockRenderBox) {
343: alignContext.addChild(new InlineBlockAlignContext(
344: (RenderBox) next));
345: } else {
346: alignContext.addChild(new NodeAlignContext(next));
347: }
348: next = next.getNext();
349: }
350:
351: while (next == null && contextStack.isEmpty() == false) {
352: // Finish the current box context, if needed
353: next = context.getNext();
354: context = (RenderBox) contextStack.pop();
355: alignContext = (BoxAlignContext) alignContextStack
356: .pop();
357: }
358: }
359: return alignContext;
360: }
361:
362: private SequenceList createHorizontalSequenceList(
363: final InlineRenderBox box) {
364: final SequenceList sequenceList = new SequenceList();
365: sequenceList.add(StartSequenceElement.INSTANCE, box);
366:
367: RenderNode next = box.getFirstChild();
368: RenderBox context = box;
369:
370: final FastStack contextStack = new FastStack();
371: boolean containsContent = false;
372:
373: while (next != null) {
374: // process next
375: if (next instanceof InlineRenderBox) {
376: final RenderBox nBox = (RenderBox) next;
377: final RenderNode firstChild = nBox.getFirstChild();
378: if (firstChild != null) {
379: // Open a non-empty box context
380: contextStack.push(context);
381: next = firstChild;
382:
383: sequenceList.add(StartSequenceElement.INSTANCE,
384: nBox);
385: context = nBox;
386: } else {
387: // Process an empty box.
388: sequenceList.add(StartSequenceElement.INSTANCE,
389: nBox);
390: sequenceList.add(EndSequenceElement.INSTANCE, nBox);
391: next = nBox.getNext();
392: }
393: } else {
394: // Process an ordinary node.
395: if (next instanceof RenderableText) {
396: sequenceList
397: .add(TextSequenceElement.INSTANCE, next);
398: containsContent = true;
399: } else if (next instanceof RenderableReplacedContent) {
400: sequenceList.add(
401: ReplacedContentSequenceElement.INSTANCE,
402: next);
403: containsContent = true;
404: } else if (next instanceof SpacerRenderNode) {
405: if (containsContent) {
406: sequenceList.add(
407: SpacerSequenceElement.INSTANCE, next);
408: }
409: } else if (next instanceof BlockRenderBox) {
410: containsContent = true;
411: sequenceList.add(InlineBoxSequenceElement.INSTANCE,
412: next);
413: } else {
414: containsContent = true;
415: sequenceList.add(
416: InlineNodeSequenceElement.INSTANCE, next);
417: }
418: next = next.getNext();
419: }
420:
421: while (next == null && contextStack.isEmpty() == false) {
422: // Finish the current box context, if needed
423: sequenceList.add(EndSequenceElement.INSTANCE, context);
424: next = context.getNext();
425: context = (RenderBox) contextStack.pop();
426: }
427: }
428:
429: sequenceList.add(EndSequenceElement.INSTANCE, box);
430: return sequenceList;
431: }
432:
433: /**
434: * Reuse the processors ..
435: *
436: * @param alignment
437: * @return
438: */
439: private LastLineTextAlignmentProcessor create(
440: final ElementAlignment alignment) {
441: if (ElementAlignment.CENTER.equals(alignment)) {
442: if (centerProcessor == null) {
443: centerProcessor = new CenterAlignmentProcessor();
444: }
445: return centerProcessor;
446: } else if (ElementAlignment.RIGHT.equals(alignment)) {
447: if (rightProcessor == null) {
448: rightProcessor = new RightAlignmentProcessor();
449: }
450: return rightProcessor;
451: }
452:
453: if (leftProcessor == null) {
454: leftProcessor = new LeftAlignmentProcessor();
455: }
456: return leftProcessor;
457: }
458:
459: private ParagraphPoolBox rebuildLastLine(
460: final ParagraphPoolBox lineBox,
461: final ParagraphPoolBox nextBox) {
462: if (lineBox == null) {
463: if (nextBox == null) {
464: throw new NullPointerException(
465: "Both Line- and Next-Line are null.");
466: }
467:
468: return rebuildLastLine(nextBox, (ParagraphPoolBox) nextBox
469: .getVisibleNext());
470: }
471:
472: if (nextBox == null) {
473: // Linebox is finished, no need to do any merging anymore..
474: return lineBox;
475: }
476:
477: boolean needToAddSpacing = true;
478:
479: // do the merging ..
480: final FastStack contextStack = new FastStack();
481: RenderNode next = nextBox.getFirstChild();
482: MergeContext context = new MergeContext(lineBox, nextBox);
483: while (next != null) {
484: // process next
485: final RenderBox writeContext = context.getWriteContext();
486: final StaticBoxLayoutProperties staticBoxLayoutProperties = writeContext
487: .getStaticBoxLayoutProperties();
488: long spaceWidth = staticBoxLayoutProperties.getSpaceWidth();
489: if (spaceWidth == 0) {
490: // Space has not been computed yet.
491: final FontMetrics fontMetrics = metaData
492: .getFontMetrics(writeContext.getStyleSheet());
493: spaceWidth = fontMetrics.getCharWidth(' ');
494: staticBoxLayoutProperties.setSpaceWidth(spaceWidth);
495: }
496:
497: if (next instanceof RenderBox) {
498: final RenderBox nBox = (RenderBox) next;
499: final RenderNode firstChild = nBox.getFirstChild();
500: if (firstChild != null) {
501: contextStack.push(context);
502: next = firstChild;
503:
504: final RenderNode writeContextLastChild = writeContext
505: .getLastChild();
506: if (writeContextLastChild instanceof RenderBox) {
507: if (writeContextLastChild.getInstanceId() == nBox
508: .getInstanceId()) {
509: context = new MergeContext(
510: (RenderBox) writeContextLastChild,
511: nBox);
512: } else {
513: if (needToAddSpacing) {
514: if (spaceWidth > 0) {
515: // docmark: Used zero as new height
516: final SpacerRenderNode spacer = new SpacerRenderNode(
517: spaceWidth, 0, false, 1);
518: spacer.setVirtualNode(true);
519: writeContext
520: .addGeneratedChild(spacer);
521: }
522: needToAddSpacing = false;
523: }
524: final RenderBox newWriter = (RenderBox) nBox
525: .derive(false);
526: newWriter.setVirtualNode(true);
527: writeContext.addGeneratedChild(newWriter);
528: context = new MergeContext(newWriter, nBox);
529: }
530: } else {
531: if (needToAddSpacing) {
532: if (spaceWidth > 0) {
533: // docmark: Used zero as new height
534: final SpacerRenderNode spacer = new SpacerRenderNode(
535: spaceWidth, 0, false, 1);
536: spacer.setVirtualNode(true);
537: writeContext.addGeneratedChild(spacer);
538: }
539: needToAddSpacing = false;
540: }
541:
542: final RenderBox newWriter = (RenderBox) nBox
543: .derive(false);
544: newWriter.setVirtualNode(true);
545: writeContext.addGeneratedChild(newWriter);
546: context = new MergeContext(newWriter, nBox);
547: }
548: } else {
549: if (needToAddSpacing) {
550: if (spaceWidth > 0) {
551: // docmark: Used zero as new height
552: final SpacerRenderNode spacer = new SpacerRenderNode(
553: spaceWidth, 0, false, 1);
554: spacer.setVirtualNode(true);
555: writeContext.addGeneratedChild(spacer);
556: }
557: needToAddSpacing = false;
558: }
559:
560: final RenderNode box = nBox.derive(true);
561: box.setVirtualNode(true);
562: writeContext.addGeneratedChild(box);
563: next = nBox.getNext();
564: }
565: } else {
566: if (needToAddSpacing) {
567: final RenderNode lastChild = writeContext
568: .getLastChild();
569: if (spaceWidth > 0
570: && (lastChild != null && lastChild instanceof SpacerRenderNode == false)) {
571: // docmark: Used zero as new height
572: final SpacerRenderNode spacer = new SpacerRenderNode(
573: spaceWidth, 0, false, 1);
574: spacer.setVirtualNode(true);
575: writeContext.addGeneratedChild(spacer);
576: }
577: needToAddSpacing = false;
578: }
579:
580: final RenderNode child = next.derive(true);
581: child.setVirtualNode(true);
582: writeContext.addGeneratedChild(child);
583: next = next.getNext();
584: }
585:
586: while (next == null && contextStack.isEmpty() == false) {
587: // Log.debug ("FINISH " + context.getReadContext());
588: next = context.getReadContext().getNext();
589: context = (MergeContext) contextStack.pop();
590: }
591: }
592:
593: return rebuildLastLine(lineBox, (ParagraphPoolBox) nextBox
594: .getVisibleNext());
595: }
596: }
|