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: * LayoutBuilder.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.layout;
030:
031: import java.awt.Shape;
032:
033: import org.jfree.fonts.encoding.CodePointBuffer;
034: import org.jfree.fonts.encoding.manual.Utf16LE;
035: import org.jfree.report.Anchor;
036: import org.jfree.report.Band;
037: import org.jfree.report.Element;
038: import org.jfree.report.ImageContainer;
039: import org.jfree.report.RootLevelBand;
040: import org.jfree.report.filter.DataSource;
041: import org.jfree.report.filter.RawDataSource;
042: import org.jfree.report.function.ExpressionRuntime;
043: import org.jfree.report.function.ProcessingContext;
044: import org.jfree.report.layout.model.BlockRenderBox;
045: import org.jfree.report.layout.model.CanvasRenderBox;
046: import org.jfree.report.layout.model.InlineRenderBox;
047: import org.jfree.report.layout.model.ParagraphRenderBox;
048: import org.jfree.report.layout.model.RenderBox;
049: import org.jfree.report.layout.model.RenderLength;
050: import org.jfree.report.layout.model.RenderNode;
051: import org.jfree.report.layout.model.RenderableReplacedContent;
052: import org.jfree.report.layout.model.context.BoxDefinition;
053: import org.jfree.report.layout.model.context.BoxDefinitionFactory;
054: import org.jfree.report.layout.output.OutputProcessorFeature;
055: import org.jfree.report.layout.output.OutputProcessorMetaData;
056: import org.jfree.report.layout.style.AnchorStyleSheet;
057: import org.jfree.report.layout.style.DynamicHeightWrapperStyleSheet;
058: import org.jfree.report.layout.style.DynamicReplacedContentStyleSheet;
059: import org.jfree.report.layout.style.NonDynamicHeightWrapperStyleSheet;
060: import org.jfree.report.layout.style.NonDynamicReplacedContentStyleSheet;
061: import org.jfree.report.layout.style.ParagraphPoolboxStyleSheet;
062: import org.jfree.report.layout.style.SectionKeepTogetherStyleSheet;
063: import org.jfree.report.layout.style.SimpleStyleSheet;
064: import org.jfree.report.layout.text.DefaultRenderableTextFactory;
065: import org.jfree.report.style.BandStyleKeys;
066: import org.jfree.report.style.BorderStyle;
067: import org.jfree.report.style.ElementStyleKeys;
068: import org.jfree.report.style.ElementStyleSheet;
069: import org.jfree.report.style.StyleKey;
070: import org.jfree.report.style.StyleSheet;
071: import org.jfree.report.style.TextStyleKeys;
072: import org.jfree.report.util.ReportDrawable;
073: import org.jfree.resourceloader.ResourceKey;
074: import org.jfree.ui.Drawable;
075:
076: /**
077: * Creation-Date: 03.04.2007, 14:59:41
078: *
079: * @author Thomas Morgner
080: */
081: public class LayoutBuilder {
082: private OutputProcessorMetaData metaData;
083: private CodePointBuffer buffer;
084: private DefaultRenderableTextFactory textFactory;
085: private TextCache textCache;
086: private StyleCache bandCache;
087: private StyleCache styleCache;
088: private StyleCache textStyleCache;
089: private BoxDefinitionFactory boxDefinitionFactory;
090: private SimpleStyleSheet bandWithKeepTogetherStyle;
091:
092: public LayoutBuilder(final OutputProcessorMetaData metaData) {
093: this .metaData = metaData;
094: this .textFactory = new DefaultRenderableTextFactory(metaData);
095: this .textCache = new TextCache();
096:
097: final StyleKey[] definedStyleKeys = StyleKey
098: .getDefinedStyleKeys();
099: final boolean paddingsDisabled = metaData
100: .isFeatureSupported(OutputProcessorFeature.DISABLE_PADDING);
101: this .bandCache = new StyleCache(definedStyleKeys,
102: paddingsDisabled);
103: this .styleCache = new StyleCache(definedStyleKeys,
104: paddingsDisabled);
105: this .textStyleCache = new StyleCache(definedStyleKeys,
106: paddingsDisabled);
107: this .boxDefinitionFactory = new BoxDefinitionFactory();
108: this .bandWithKeepTogetherStyle = new SimpleStyleSheet(
109: new SectionKeepTogetherStyleSheet(true),
110: definedStyleKeys);
111: }
112:
113: private String getStyleFromLayoutManager(final Band band) {
114: final Object layoutManager = band.getStyle().getStyleProperty(
115: BandLayoutManager.LAYOUTMANAGER);
116: if (layoutManager == null) {
117: return "canvas";
118: }
119: if (layoutManager instanceof StackedLayoutManager) {
120: return "block";
121: }
122: return "canvas";
123: }
124:
125: public void add(final RenderBox parent, final Band band,
126: final ExpressionRuntime runtime, final Object stateKey) {
127: final boolean parentIsInlineContainer = (parent instanceof InlineRenderBox || parent instanceof ParagraphRenderBox);
128: if (isEmpty(band)) {
129: if (isControlBand(band)) {
130: final RenderBox box = produceBox(band, stateKey,
131: parentIsInlineContainer);
132: parent.addChild(box);
133: box.close();
134: } else if (band instanceof RootLevelBand) {
135: final BlockRenderBox markerBox = new BlockRenderBox(
136: bandWithKeepTogetherStyle, BoxDefinition.EMPTY,
137: stateKey);
138: parent.addChild(markerBox);
139: markerBox.close();
140: }
141: return;
142: }
143:
144: final RenderBox box = produceBox(band, stateKey,
145: parentIsInlineContainer);
146: ParagraphRenderBox paragraphBox = null;
147: if (box instanceof InlineRenderBox
148: && parentIsInlineContainer == false) {
149: // Normalize the rendering-model. Inline-Boxes must always be contained in Paragraph-Boxes ..
150: final BoxDefinition boxDefinition = boxDefinitionFactory
151: .getBoxDefinition(band.getStyle());
152: paragraphBox = new ParagraphRenderBox(box.getStyleSheet(),
153: boxDefinition, stateKey);
154: paragraphBox.setName(band.getName());
155: paragraphBox.getBoxDefinition().setPreferredWidth(
156: RenderLength.AUTO);
157: paragraphBox.addChild(box);
158:
159: parent.addChild(paragraphBox);
160: } else {
161: parent.addChild(box);
162: }
163:
164: final Element[] elementArray = band.getElementArray();
165: final int elementCount = elementArray.length;
166: for (int i = 0; i < elementCount; i++) {
167: final Element element = elementArray[i];
168: if (element instanceof Band) {
169: final Band childBand = (Band) element;
170: add(box, childBand, runtime, stateKey);
171: } else {
172: if (element.isVisible() == false) {
173: continue;
174: }
175:
176: final Object value = element.getValue(runtime);
177: if (metaData.isContentSupported(value)) {
178: if (value instanceof ReportDrawable) {
179: // A report drawable element receives some context information as well.
180: final ReportDrawable reportDrawable = (ReportDrawable) value;
181: final ProcessingContext processingContext = runtime
182: .getProcessingContext();
183: reportDrawable
184: .setLayoutSupport(processingContext
185: .getLayoutSupport());
186: reportDrawable
187: .setConfiguration(processingContext
188: .getConfiguration());
189: reportDrawable
190: .setResourceBundleFactory(processingContext
191: .getResourceBundleFactory());
192: processReportDrawableContent(reportDrawable,
193: box, element, stateKey);
194: } else if (value instanceof Anchor) {
195: processAnchor((Anchor) value, box, element,
196: stateKey);
197: } else if (value != null) {
198: final DataSource dataSource = element
199: .getDataSource();
200: final Object rawValue;
201: if (dataSource instanceof RawDataSource) {
202: final RawDataSource rds = (RawDataSource) dataSource;
203: rawValue = rds.getRawValue(runtime);
204: } else {
205: rawValue = null;
206: }
207:
208: if (value instanceof ImageContainer
209: || value instanceof Shape
210: || value instanceof Drawable) {
211: processReplacedContent(value, rawValue,
212: box, element, stateKey);
213: } else {
214: processText(value, rawValue, box, element,
215: stateKey);
216: }
217: }
218: // ignore null values ..
219: }
220: }
221: }
222:
223: if (paragraphBox != null) {
224: paragraphBox.close();
225: }
226:
227: box.close();
228: }
229:
230: private boolean isControlBand(final Band band) {
231: final ElementStyleSheet style = band.getStyle();
232: if (style.getStyleProperty(BandStyleKeys.COMPUTED_SHEETNAME) != null) {
233: return true;
234: }
235: if (style.getStyleProperty(BandStyleKeys.BOOKMARK) != null) {
236: return true;
237: }
238: if ("inline".equals(style
239: .getStyleProperty(BandStyleKeys.LAYOUT)) == false) {
240: if (Boolean.TRUE.equals(style
241: .getStyleProperty(BandStyleKeys.PAGEBREAK_AFTER))) {
242: return true;
243: }
244: if (Boolean.TRUE.equals(style
245: .getStyleProperty(BandStyleKeys.PAGEBREAK_BEFORE))) {
246: return true;
247: }
248: }
249: return false;
250: }
251:
252: private boolean isEmpty(final Band band) {
253: if (band.isVisible() == false) {
254: return true;
255: }
256:
257: if (band.getElementCount() > 0) {
258: return false;
259: }
260:
261: // A band is empty, if it has a defined minimum or preferred height
262: final ElementStyleSheet style = band.getStyle();
263: if (isLengthDefined(style.getStyleProperty(
264: ElementStyleKeys.HEIGHT, null))) {
265: return false;
266: }
267: if (isLengthDefined(style.getStyleProperty(
268: ElementStyleKeys.WIDTH, null))) {
269: return false;
270: }
271: if (isLengthDefined(style.getStyleProperty(
272: ElementStyleKeys.POS_Y, null))) {
273: return false;
274: }
275: if (isLengthDefined(style.getStyleProperty(
276: ElementStyleKeys.POS_X, null))) {
277: return false;
278: }
279: if (isLengthDefined(style.getStyleProperty(
280: ElementStyleKeys.MIN_HEIGHT, null))) {
281: return false;
282: }
283: // if (isLengthDefined(style.getStyleProperty(ElementStyleKeys.MIN_WIDTH, null)))
284: // {
285: // return false;
286: // }
287: if (isLengthDefined(style.getStyleProperty(
288: ElementStyleKeys.PADDING_TOP, null))) {
289: return false;
290: }
291: if (isLengthDefined(style.getStyleProperty(
292: ElementStyleKeys.PADDING_LEFT, null))) {
293: return false;
294: }
295: if (isLengthDefined(style.getStyleProperty(
296: ElementStyleKeys.PADDING_BOTTOM, null))) {
297: return false;
298: }
299: if (isLengthDefined(style.getStyleProperty(
300: ElementStyleKeys.PADDING_RIGHT, null))) {
301: return false;
302: }
303: if (BorderStyle.NONE
304: .equals(style.getStyleProperty(
305: ElementStyleKeys.BORDER_BOTTOM_STYLE,
306: BorderStyle.NONE)) == false) {
307: return false;
308: }
309: if (BorderStyle.NONE.equals(style.getStyleProperty(
310: ElementStyleKeys.BORDER_TOP_STYLE, BorderStyle.NONE)) == false) {
311: return false;
312: }
313: if (BorderStyle.NONE.equals(style.getStyleProperty(
314: ElementStyleKeys.BORDER_LEFT_STYLE, BorderStyle.NONE)) == false) {
315: return false;
316: }
317: if (BorderStyle.NONE.equals(style.getStyleProperty(
318: ElementStyleKeys.BORDER_RIGHT_STYLE, BorderStyle.NONE)) == false) {
319: return false;
320: }
321: return true;
322: }
323:
324: private boolean isLengthDefined(final Object o) {
325: if (o == null) {
326: return false;
327: }
328: if (o instanceof Number == false) {
329: return false;
330: }
331: final Number n = (Number) o;
332: return n.doubleValue() != 0;
333: }
334:
335: private void processText(final Object value, final Object rawValue,
336: final RenderBox box, final Element element,
337: final Object stateKey) {
338:
339: final String text;
340: if (element.getStyle().getBooleanStyleProperty(
341: TextStyleKeys.TRIM_TEXT_CONTENT)) {
342: text = String.valueOf(value).trim();
343: } else {
344: text = String.valueOf(value);
345: }
346:
347: final ElementStyleSheet style = element.getStyle();
348: final TextCache.Result result = textCache.get(style.getId(),
349: style.getChangeTracker(), text);
350: if (result != null) {
351: addTextNodes(element.getName(), rawValue, result.getText(),
352: result.getFinish(), box, result.getStyleSheet(),
353: stateKey);
354: return;
355: }
356:
357: final SimpleStyleSheet elementStyle;
358: if (box instanceof CanvasRenderBox) {
359: if (element.isDynamicContent() == false) {
360: elementStyle = textStyleCache
361: .getStyleSheet(new NonDynamicHeightWrapperStyleSheet(
362: style));
363: } else {
364: elementStyle = styleCache
365: .getStyleSheet(new DynamicHeightWrapperStyleSheet(
366: style));
367: }
368: } else {
369: elementStyle = styleCache.getStyleSheet(style);
370: }
371:
372: if (buffer != null) {
373: buffer.setCursor(0);
374: }
375:
376: buffer = Utf16LE.getInstance().decodeString(text, buffer);
377: final int[] data = buffer.getBuffer();
378:
379: if (box instanceof InlineRenderBox == false) {
380: textFactory.startText();
381: }
382:
383: final RenderNode[] renderNodes = textFactory.createText(data,
384: 0, buffer.getLength(), elementStyle);
385: final RenderNode[] finishNodes = textFactory.finishText();
386:
387: addTextNodes(element.getName(), rawValue, renderNodes,
388: finishNodes, box, elementStyle, stateKey);
389: textCache.store(style.getId(), style.getChangeTracker(), text,
390: elementStyle, renderNodes, finishNodes);
391: }
392:
393: private void processReportDrawableContent(
394: final ReportDrawable value, final RenderBox box,
395: final Element element, final Object stateKey) {
396: final SimpleStyleSheet elementStyle;
397:
398: if (element.isDynamicContent() == false) {
399: elementStyle = textStyleCache
400: .getStyleSheet(new NonDynamicReplacedContentStyleSheet(
401: element.getStyle()));
402: } else {
403: elementStyle = styleCache
404: .getStyleSheet(new DynamicReplacedContentStyleSheet(
405: element.getStyle()));
406: }
407: final RenderableReplacedContent child = new RenderableReplacedContent(
408: elementStyle, value, null, metaData);
409: value.setStyleSheet(elementStyle);
410:
411: if (box instanceof InlineRenderBox) {
412: box.addChild(child);
413: } else // add the replaced content into a ordinary block box. There's no need to create a full paragraph for it
414: {
415: final SimpleStyleSheet styleSheet;
416: if (element.isDynamicContent() == false) {
417: styleSheet = bandCache
418: .getStyleSheet(new NonDynamicHeightWrapperStyleSheet(
419: element.getStyle()));
420: } else {
421: styleSheet = bandCache
422: .getStyleSheet(element.getStyle());
423: }
424: final BoxDefinition boxDefinition = boxDefinitionFactory
425: .getBoxDefinition(styleSheet);
426: final RenderBox autoParagraphBox = new CanvasRenderBox(
427: styleSheet, boxDefinition, stateKey);
428: autoParagraphBox.setName(element.getName());
429: autoParagraphBox.getBoxDefinition().setPreferredWidth(
430: RenderLength.AUTO);
431: autoParagraphBox.addChild(child);
432: autoParagraphBox.close();
433: box.addChild(autoParagraphBox);
434: }
435: }
436:
437: private void processAnchor(final Anchor anchor,
438: final RenderBox box, final Element element,
439: final Object stateKey) {
440: final String anchorName = anchor.getName();
441:
442: if (box instanceof InlineRenderBox) {
443: final SimpleStyleSheet styleSheet = bandCache
444: .getStyleSheet(new NonDynamicHeightWrapperStyleSheet(
445: new AnchorStyleSheet(anchorName, element
446: .getStyle())));
447: final BoxDefinition boxDefinition = boxDefinitionFactory
448: .getBoxDefinition(styleSheet);
449: final RenderBox autoParagraphBox = new InlineRenderBox(
450: styleSheet, boxDefinition, stateKey);
451: autoParagraphBox.setName(element.getName());
452: autoParagraphBox.getBoxDefinition().setPreferredWidth(
453: RenderLength.AUTO);
454: autoParagraphBox.close();
455: box.addChild(autoParagraphBox);
456: } else // add the replaced content into a ordinary block box. There's no need to create a full paragraph for it
457: {
458: final SimpleStyleSheet styleSheet = bandCache
459: .getStyleSheet(new NonDynamicHeightWrapperStyleSheet(
460: new AnchorStyleSheet(anchorName, element
461: .getStyle())));
462: final BoxDefinition boxDefinition = boxDefinitionFactory
463: .getBoxDefinition(styleSheet);
464: final RenderBox autoParagraphBox = new CanvasRenderBox(
465: styleSheet, boxDefinition, stateKey);
466: autoParagraphBox.setName(element.getName());
467: autoParagraphBox.getBoxDefinition().setPreferredWidth(
468: RenderLength.AUTO);
469: autoParagraphBox.close();
470: box.addChild(autoParagraphBox);
471: }
472: }
473:
474: private void processReplacedContent(final Object value,
475: final Object rawValue, final RenderBox box,
476: final Element element, final Object stateKey) {
477: final SimpleStyleSheet elementStyle;
478: if (box instanceof CanvasRenderBox) {
479: if (element.isDynamicContent() == false) {
480: elementStyle = textStyleCache
481: .getStyleSheet(new NonDynamicReplacedContentStyleSheet(
482: element.getStyle()));
483: } else {
484: elementStyle = styleCache
485: .getStyleSheet(new DynamicReplacedContentStyleSheet(
486: element.getStyle()));
487: }
488: } else {
489: elementStyle = styleCache.getStyleSheet(element.getStyle());
490: }
491:
492: final ResourceKey rawKey;
493: if (rawValue instanceof ResourceKey) {
494: rawKey = (ResourceKey) rawValue;
495: } else {
496: rawKey = null;
497: }
498:
499: final RenderableReplacedContent child = new RenderableReplacedContent(
500: elementStyle, value, rawKey, metaData);
501:
502: if (box instanceof InlineRenderBox) {
503: box.addChild(child);
504: } else // add the replaced content into a ordinary block box. There's no need to create a full paragraph for it
505: {
506: final SimpleStyleSheet styleSheet;
507: if (element.isDynamicContent() == false
508: && box instanceof CanvasRenderBox) {
509: styleSheet = bandCache
510: .getStyleSheet(new NonDynamicHeightWrapperStyleSheet(
511: element.getStyle()));
512: } else {
513: styleSheet = bandCache
514: .getStyleSheet(element.getStyle());
515: }
516: final BoxDefinition boxDefinition = boxDefinitionFactory
517: .getBoxDefinition(styleSheet);
518: final RenderBox autoParagraphBox = new CanvasRenderBox(
519: styleSheet, boxDefinition, stateKey);
520: autoParagraphBox.setName(element.getName());
521: autoParagraphBox.getBoxDefinition().setPreferredWidth(
522: RenderLength.AUTO);
523: autoParagraphBox.addChild(child);
524: autoParagraphBox.close();
525: box.addChild(autoParagraphBox);
526: }
527: }
528:
529: private void addTextNodes(final String name, final Object rawValue,
530: final RenderNode[] renderNodes,
531: final RenderNode[] finishNodes, final RenderBox box,
532: final StyleSheet elementStyle, final Object stateKey) {
533: if (box instanceof InlineRenderBox) {
534: final StyleSheet styleSheet = bandCache
535: .getStyleSheet(elementStyle);
536: final BoxDefinition boxDefinition = boxDefinitionFactory
537: .getBoxDefinition(styleSheet);
538: final InlineRenderBox autoParagraphBox = new InlineRenderBox(
539: styleSheet, boxDefinition, stateKey);
540: autoParagraphBox.setName(name);
541: autoParagraphBox.getBoxDefinition().setPreferredWidth(
542: RenderLength.AUTO);
543: autoParagraphBox.addChilds(renderNodes);
544: autoParagraphBox.addChilds(finishNodes);
545: autoParagraphBox.close();
546: box.addChild(autoParagraphBox);
547: } else {
548: final StyleSheet styleSheet = bandCache
549: .getStyleSheet(elementStyle);
550: final BoxDefinition boxDefinition = boxDefinitionFactory
551: .getBoxDefinition(styleSheet);
552: final ParagraphRenderBox autoParagraphBox = new ParagraphRenderBox(
553: styleSheet, boxDefinition, stateKey);
554: autoParagraphBox.setRawValue(rawValue);
555: autoParagraphBox.setName(name);
556: autoParagraphBox.getBoxDefinition().setPreferredWidth(
557: RenderLength.AUTO);
558: autoParagraphBox.addChilds(renderNodes);
559: autoParagraphBox.addChilds(finishNodes);
560: autoParagraphBox.close();
561: box.addChild(autoParagraphBox);
562: }
563: }
564:
565: private RenderBox produceBox(final Band band,
566: final Object stateKey, final boolean parentIsInlineBox) {
567: Object layoutType = band.getStyle().getStyleProperty(
568: BandStyleKeys.LAYOUT, null);
569: if (layoutType == null) {
570: layoutType = getStyleFromLayoutManager(band);
571: }
572: if (parentIsInlineBox) {
573: layoutType = "inline";
574: }
575:
576: // todo: Check for cachability ..
577: final RenderBox box;
578: if ("block".equals(layoutType)) {
579: final SimpleStyleSheet styleSheet = bandCache
580: .getStyleSheet(band.getStyle());
581: final BoxDefinition boxDefinition = boxDefinitionFactory
582: .getBoxDefinition(band.getStyle());
583: box = new BlockRenderBox(styleSheet, boxDefinition,
584: stateKey);
585: } else if ("inline".equals(layoutType)) {
586: if (parentIsInlineBox) {
587: final SimpleStyleSheet styleSheet = bandCache
588: .getStyleSheet(band.getStyle());
589: final BoxDefinition boxDefinition = boxDefinitionFactory
590: .getBoxDefinition(band.getStyle());
591: box = new InlineRenderBox(styleSheet, boxDefinition,
592: stateKey);
593: } else {
594: final SimpleStyleSheet styleSheet = bandCache
595: .getStyleSheet(new ParagraphPoolboxStyleSheet(
596: band.getStyle()));
597: final BoxDefinition boxDefinition = boxDefinitionFactory
598: .getBoxDefinition(styleSheet);
599: box = new InlineRenderBox(styleSheet, boxDefinition,
600: stateKey);
601: }
602: } else // assume 'Canvas' by default ..
603: {
604: final SimpleStyleSheet styleSheet = bandCache
605: .getStyleSheet(band.getStyle());
606: final BoxDefinition boxDefinition = boxDefinitionFactory
607: .getBoxDefinition(band.getStyle());
608: box = new CanvasRenderBox(styleSheet, boxDefinition,
609: stateKey);
610: }
611:
612: // for the sake of debugging ..
613: final String name = band.getName();
614: if (name != null
615: && name.startsWith(Band.ANONYMOUS_BAND_PREFIX) == false) {
616: box.setName(name);
617: }
618: return box;
619: }
620: }
|