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: * ParagraphLineBreakStep.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.layout.model.BlockRenderBox;
032: import org.jfree.report.layout.model.CanvasRenderBox;
033: import org.jfree.report.layout.model.InlineRenderBox;
034: import org.jfree.report.layout.model.LogicalPageBox;
035: import org.jfree.report.layout.model.ParagraphRenderBox;
036: import org.jfree.report.layout.model.RenderBox;
037: import org.jfree.report.layout.model.RenderNode;
038: import org.jfree.report.layout.model.RenderableText;
039: import org.jfree.report.layout.process.linebreak.EmptyLinebreaker;
040: import org.jfree.report.layout.process.linebreak.FullLinebreaker;
041: import org.jfree.report.layout.process.linebreak.ParagraphLinebreaker;
042: import org.jfree.report.layout.process.linebreak.SimpleLinebreaker;
043: import org.jfree.util.FastStack;
044:
045: /**
046: * This static computation step performs manual linebreaks on all paragraphs.
047: * This transforms the pool-collection into the lines-collection.
048: * <p/>
049: * For now, we follow a very simple path: A paragraph cannot be validated, if it
050: * is not yet closed. The linebreaking, be it the static one here or the dynamic
051: * one later, must be redone when the paragraph changes.
052: * <p/>
053: * Splitting for linebreaks happens only between inline-boxes. BlockBoxes that
054: * are contained in inline-boxes (like 'inline-block' elements or
055: * 'inline-tables') are considered unbreakable according to the CSS specs.
056: * Linebreaking can be suspended in these cases.
057: * <p/>
058: * As paragraphs itself are block elements, the linebreaks can be done
059: * iterative, using a simple stack to store the context of possibly nested
060: * paragraphs. The paragraph's pool contains the elements that should be
061: * processed, and the line-container will receive the pool's content (contained
062: * in an artificial inline element, as the linecontainer is a block-level
063: * element).
064: * <p/>
065: * Change-tracking should take place on the paragraph's pool element instead of
066: * the paragraph itself. This way, only structural changes are taken into
067: * account.
068: *
069: * @author Thomas Morgner
070: */
071: public final class ParagraphLineBreakStep extends
072: IterateStructuralProcessStep {
073: private static final EmptyLinebreaker LEAF_BREAK_STATE = new EmptyLinebreaker();
074:
075: private FastStack paragraphNesting;
076: private ParagraphLinebreaker breakState;
077:
078: public ParagraphLineBreakStep() {
079: paragraphNesting = new FastStack();
080: }
081:
082: public void compute(final LogicalPageBox root) {
083: paragraphNesting.clear();
084: try {
085: startProcessing(root);
086: } finally {
087: paragraphNesting.clear();
088: breakState = null;
089: }
090: }
091:
092: protected boolean startBlockBox(final BlockRenderBox box) {
093: if (box instanceof ParagraphRenderBox) {
094: final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
095: final long poolChangeTracker = paragraphBox.getPool()
096: .getChangeTracker();
097: final boolean unchanged = poolChangeTracker == paragraphBox
098: .getLineBoxAge();
099:
100: if (unchanged) {
101: // If the paragraph is unchanged (no new elements have been added to the pool) then we can take a
102: // shortcut. The childs of this paragraph will also be unchanged (as any structural change would have increased
103: // the change-tracker).
104: paragraphNesting.push(LEAF_BREAK_STATE);
105: breakState = LEAF_BREAK_STATE;
106: return false;
107: }
108:
109: // When the paragraph has changed, this can only be caused by someone adding a new node to the paragraph
110: // or to one of the childs.
111:
112: // Paragraphs can be nested whenever a Inline-Level element declares to be a Block-Layouter. (This is an
113: // Inline-Block or Inline-Table case in CSS)
114:
115: // It is guaranteed, that if a child is changed, the parent is marked as changed as well.
116: // So we have only two cases to deal with: (1) The child is unchanged (2) the child is changed.
117:
118: if (breakState == null) {
119: if (paragraphBox.isComplexParagraph()) {
120: final ParagraphLinebreaker item = new FullLinebreaker(
121: paragraphBox);
122: paragraphNesting.push(item);
123: breakState = item;
124: } else {
125: final ParagraphLinebreaker item = new SimpleLinebreaker(
126: paragraphBox);
127: paragraphNesting.push(item);
128: breakState = item;
129: }
130: return true;
131: }
132:
133: // The breakState indicates that there is a paragraph processing active at the moment. This means, the
134: // paragraph-box we are dealing with right now is a nested box.
135:
136: if (breakState.isWritable() == false) {
137: // OK, should not happen, but you never know. I'm good at hiding
138: // bugs in the code ..
139: throw new IllegalStateException(
140: "A child cannot be dirty, if the parent is clean");
141: }
142:
143: // The paragraph is somehow nested in an other paragraph.
144: // This cannot be handled by the simple implementation, as we will most likely start to deriveForAdvance childs sooner
145: // or later
146: if (breakState instanceof FullLinebreaker == false) {
147: // convert it ..
148: final FullLinebreaker fullBreaker = breakState
149: .startComplexLayout();
150: paragraphNesting.pop();
151: paragraphNesting.push(fullBreaker);
152: breakState = fullBreaker;
153: }
154:
155: final ParagraphLinebreaker subFlow = breakState
156: .startParagraphBox(paragraphBox);
157: paragraphNesting.push(subFlow);
158: breakState = subFlow;
159: return true;
160: }
161:
162: // some other block box ..
163: if (breakState == null) {
164: if (box.getChangeTracker() == box.getCachedAge()) {
165: return false;
166: }
167: // Not nested in a paragraph, thats easy ..
168: return true;
169: }
170:
171: if (breakState.isWritable() == false) {
172: throw new IllegalStateException(
173: "This cannot be: There is an active break-state, but the box is not writable.");
174: }
175:
176: breakState.startBlockBox(box);
177: return true;
178: }
179:
180: public boolean startCanvasBox(final CanvasRenderBox box) {
181: if (breakState == null) {
182: if (box.getChangeTracker() == box.getCachedAge()) {
183: return false;
184: }
185:
186: return true;
187: }
188:
189: // some other block box .. suspend.
190: if (breakState.isWritable() == false) {
191: throw new IllegalStateException(
192: "A child cannot be dirty, if the parent is clean");
193: }
194:
195: breakState.startBlockBox(box);
196: return true;
197: }
198:
199: public void finishCanvasBox(final CanvasRenderBox box) {
200: if (breakState != null) {
201: if (breakState.isWritable() == false) {
202: throw new IllegalStateException(
203: "A child cannot be dirty, if the parent is clean");
204: }
205:
206: breakState.finishBlockBox(box);
207: }
208: }
209:
210: protected void finishBlockBox(final BlockRenderBox box) {
211: if (box instanceof ParagraphRenderBox) {
212: // do the linebreak jiggle ...
213: // This is the first test case whether it is possible to avoid
214: // composition-recursion on such computations. I'd prefer to have
215: // an iterator pattern here ...
216:
217: // finally update the change tracker ..
218: breakState.finish();
219: paragraphNesting.pop();
220: if (paragraphNesting.isEmpty()) {
221: breakState = null;
222: } else {
223: breakState = (ParagraphLinebreaker) paragraphNesting
224: .peek();
225: breakState.finishParagraphBox((ParagraphRenderBox) box);
226: }
227: return;
228: }
229:
230: if (breakState == null) {
231: return;
232: }
233:
234: if (breakState.isWritable() == false) {
235: throw new IllegalStateException(
236: "A child cannot be dirty, if the parent is clean");
237: }
238:
239: breakState.finishBlockBox(box);
240: }
241:
242: protected boolean startInlineBox(final InlineRenderBox box) {
243: if (breakState == null || breakState.isWritable() == false) {
244: if (box.getChangeTracker() == box.getCachedAge()) {
245: return false;
246: }
247: return true;
248: }
249:
250: breakState.startInlineBox(box);
251: return true;
252: }
253:
254: protected void finishInlineBox(final InlineRenderBox box) {
255: if (breakState == null || breakState.isWritable() == false) {
256: return;
257: }
258:
259: breakState.finishInlineBox(box);
260: if (breakState.isBreakRequested() && box.getNext() != null) {
261: performBreak();
262: }
263: }
264:
265: protected void processOtherNode(final RenderNode node) {
266: if (breakState == null || breakState.isWritable() == false) {
267: return;
268: }
269: if (breakState.isSuspended()
270: || node instanceof RenderableText == false) {
271: breakState.addNode(node);
272: return;
273: }
274:
275: final RenderableText text = (RenderableText) node;
276: breakState.addNode(text);
277: if (text.isForceLinebreak() == false) {
278: return;
279: }
280:
281: // OK, someone requested a manual linebreak.
282: // Fill a stack with the current context ..
283: // Check if we are at the end of the line
284: if (node.getNext() == null) {
285: // OK, if we are at the end of the line (for all contexts), so we
286: // dont have to perform a break. The text will end anyway ..
287: if (isEndOfLine(node)) {
288: return;
289: }
290:
291: // as soon as we are no longer the last element - break!
292: // According to the flow rules, that will happen in one of the next
293: // finishInlineBox events ..
294: breakState.setBreakRequested(true);
295: return;
296: }
297:
298: performBreak();
299: }
300:
301: private boolean isEndOfLine(final RenderNode node) {
302: boolean endOfLine = true;
303: RenderBox parent = node.getParent();
304: while (parent != null) {
305: if (parent instanceof InlineRenderBox == false) {
306: break;
307: }
308: if (parent.getNext() != null) {
309: endOfLine = false;
310: break;
311: }
312: parent = parent.getParent();
313: }
314: return endOfLine;
315: }
316:
317: private void performBreak() {
318: if (breakState instanceof FullLinebreaker == false) {
319: final FullLinebreaker fullBreaker = breakState
320: .startComplexLayout();
321: paragraphNesting.pop();
322: paragraphNesting.push(fullBreaker);
323: breakState = fullBreaker;
324:
325: fullBreaker.performBreak();
326: } else {
327: final FullLinebreaker fullBreaker = (FullLinebreaker) breakState;
328: fullBreaker.performBreak();
329: }
330: }
331:
332: protected boolean startOtherBox(final RenderBox box) {
333: if (breakState == null) {
334: return false;
335: }
336:
337: if (breakState.isWritable() == false) {
338: return false;
339: }
340:
341: breakState.startBlockBox(box);
342: return true;
343: }
344:
345: protected void finishOtherBox(final RenderBox box) {
346: if (breakState != null && breakState.isWritable()) {
347: breakState.finishBlockBox(box);
348: }
349: }
350: }
|