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: * DefaultTextExtractor.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.output.table.base;
030:
031: import org.jfree.fonts.encoding.CodePointBuffer;
032: import org.jfree.fonts.encoding.CodePointStream;
033: import org.jfree.fonts.encoding.manual.Utf16LE;
034: import org.jfree.report.layout.model.BlockRenderBox;
035: import org.jfree.report.layout.model.CanvasRenderBox;
036: import org.jfree.report.layout.model.ParagraphRenderBox;
037: import org.jfree.report.layout.model.RenderBox;
038: import org.jfree.report.layout.model.RenderNode;
039: import org.jfree.report.layout.model.RenderableReplacedContent;
040: import org.jfree.report.layout.model.RenderableText;
041: import org.jfree.report.layout.model.SpacerRenderNode;
042: import org.jfree.report.layout.output.OutputProcessorMetaData;
043: import org.jfree.report.layout.process.IterateStructuralProcessStep;
044: import org.jfree.report.layout.process.RevalidateTextEllipseProcessStep;
045: import org.jfree.report.layout.text.Glyph;
046: import org.jfree.report.style.ElementStyleKeys;
047: import org.jfree.report.util.geom.StrictBounds;
048: import org.jfree.util.Log;
049:
050: /**
051: * Creation-Date: 02.11.2007, 14:14:23
052: *
053: * @author Thomas Morgner
054: */
055: public class DefaultTextExtractor extends IterateStructuralProcessStep {
056: private StringBuffer text;
057: private Object rawResult;
058: private RenderNode rawSource;
059: private StrictBounds paragraphBounds;
060: private boolean textLineOverflow;
061: private RevalidateTextEllipseProcessStep revalidateTextEllipseProcessStep;
062: private CodePointBuffer codePointBuffer;
063: private boolean manualBreak;
064: private long contentAreaX1;
065: private long contentAreaX2;
066: private boolean ellipseDrawn;
067:
068: public DefaultTextExtractor(final OutputProcessorMetaData metaData) {
069: text = new StringBuffer(400);
070: paragraphBounds = new StrictBounds();
071: revalidateTextEllipseProcessStep = new RevalidateTextEllipseProcessStep(
072: metaData);
073: }
074:
075: public Object compute(final RenderBox box) {
076: rawResult = null;
077: rawSource = null;
078: // initialize it once. It may be overriden later, if there is a real paragraph
079: paragraphBounds.setRect(box.getX(), box.getY(), box.getWidth(),
080: box.getHeight());
081: clearText();
082: startProcessing(box);
083:
084: // A simple result. So there's no need to create a rich-text string.
085: if (rawResult != null) {
086: return rawResult;
087: }
088: return text.toString();
089: }
090:
091: private long extractEllipseSize(final RenderNode node) {
092: if (node == null) {
093: return 0;
094: }
095: final RenderBox parent = node.getParent();
096: if (parent == null) {
097: return 0;
098: }
099: final RenderBox textEllipseBox = parent.getTextEllipseBox();
100: if (textEllipseBox == null) {
101: return 0;
102: }
103: return textEllipseBox.getWidth();
104: }
105:
106: protected void processOtherNode(final RenderNode node) {
107: if (node.isNodeVisible(paragraphBounds) == false) {
108: return;
109: }
110:
111: if (node.isVirtualNode()) {
112: if (ellipseDrawn) {
113: return;
114: }
115: ellipseDrawn = true;
116:
117: final RenderBox parent = node.getParent();
118: if (parent != null) {
119: final RenderBox textEllipseBox = parent
120: .getTextEllipseBox();
121: if (textEllipseBox != null) {
122: processBoxChilds(textEllipseBox);
123: }
124: }
125: return;
126: }
127:
128: if (node instanceof RenderableText) {
129: final RenderableText textNode = (RenderableText) node;
130: if (isTextLineOverflow()) {
131: final long ellipseSize = extractEllipseSize(node);
132: final long x1 = node.getX();
133: final long x2 = x1 + node.getWidth();
134: final long effectiveAreaX2 = (contentAreaX2 - ellipseSize);
135: if (x2 <= effectiveAreaX2) {
136: // the text will be fully visible.
137: drawText(textNode, x2);
138: } else if (x1 >= contentAreaX2) {
139: // Skip, the node will not be visible.
140: } else {
141: // The text node that is printed will overlap with the ellipse we need to print.
142: drawText(textNode, effectiveAreaX2);
143: final RenderBox parent = node.getParent();
144: if (parent != null) {
145: final RenderBox textEllipseBox = parent
146: .getTextEllipseBox();
147: if (textEllipseBox != null) {
148: processBoxChilds(textEllipseBox);
149: }
150: }
151: }
152:
153: } else {
154: drawText(textNode, textNode.getX()
155: + textNode.getWidth());
156: }
157: if (textNode.isForceLinebreak()) {
158: manualBreak = true;
159: }
160: } else if (node instanceof SpacerRenderNode) {
161: final SpacerRenderNode spacer = (SpacerRenderNode) node;
162: final int count = Math.min(1, spacer.getSpaceCount());
163: for (int i = 0; i < count; i++) {
164: this .text.append(' ');
165: }
166: }
167: }
168:
169: /**
170: * Renders the glyphs stored in the text node.
171: *
172: * @param renderableText the text node that should be rendered.
173: * @param contentX2
174: */
175: protected void drawText(final RenderableText renderableText,
176: final long contentX2) {
177: if (renderableText.getLength() == 0) {
178: // This text is empty.
179: return;
180: }
181: if (renderableText.isNodeVisible(paragraphBounds) == false) {
182: return;
183: }
184:
185: if (contentX2 >= (renderableText.getX() + renderableText
186: .getWidth())) {
187: this .text.append(renderableText.getRawText());
188: } else {
189: final Glyph[] gs = renderableText.getGlyphs();
190: final int maxLength = computeMaximumTextSize(
191: renderableText, contentX2);
192: this .text.append(textToString(gs, renderableText
193: .getOffset(), maxLength));
194: }
195: }
196:
197: protected String textToString(final Glyph[] gs, final int offset,
198: final int length) {
199: if (length == 0) {
200: return "";
201: }
202:
203: if (codePointBuffer == null) {
204: codePointBuffer = new CodePointBuffer(length);
205: } else {
206: codePointBuffer.setCursor(0);
207: }
208: //noinspection SynchronizeOnNonFinalField
209: synchronized (codePointBuffer) {
210: final CodePointStream cps = new CodePointStream(
211: codePointBuffer, length);
212: final int maxPos = offset + length;
213: for (int i = offset; i < maxPos; i++) {
214: final Glyph g = gs[i];
215: cps.put(g.getCodepoint());
216: final int[] extraChars = g.getExtraChars();
217: if (extraChars.length > 0) {
218: cps.put(extraChars);
219: }
220: }
221: cps.close();
222: return Utf16LE.getInstance().encodeString(codePointBuffer);
223: }
224: }
225:
226: protected int computeMaximumTextSize(final RenderableText node,
227: final long contentX2) {
228: final int length = node.getLength();
229: final long x = node.getX();
230: if (contentX2 >= (x + node.getWidth())) {
231: return length;
232: }
233:
234: final Glyph[] gs = node.getGlyphs();
235: long runningPos = x;
236: final int offset = node.getOffset();
237: final int maxPos = offset + length;
238:
239: for (int i = offset; i < maxPos; i++) {
240: final Glyph g = gs[i];
241: runningPos += g.getWidth();
242: if (runningPos > contentX2) {
243: return Math.max(0, i - offset);
244: }
245: }
246: return length;
247: }
248:
249: protected boolean startOtherBox(final RenderBox box) {
250: return false;
251: }
252:
253: protected boolean isContentField(final RenderBox box) {
254: final RenderNode firstChild = box.getFirstChild();
255: return (firstChild instanceof RenderableReplacedContent && firstChild == box
256: .getLastChild());
257: }
258:
259: public boolean startCanvasBox(final CanvasRenderBox box) {
260: if (isContentField(box)) {
261: final RenderableReplacedContent rpc = (RenderableReplacedContent) box
262: .getFirstChild();
263: this .rawResult = rpc.getRawObject();
264: this .rawSource = rpc;
265: return false;
266: }
267: return false;
268: }
269:
270: protected boolean startBlockBox(final BlockRenderBox box) {
271: if (isContentField(box)) {
272: final RenderableReplacedContent rpc = (RenderableReplacedContent) box
273: .getFirstChild();
274: this .rawResult = rpc.getRawObject();
275: this .rawSource = rpc;
276: return false;
277: }
278: return true;
279: }
280:
281: public RenderNode getRawSource() {
282: return rawSource;
283: }
284:
285: protected void processParagraphChilds(final ParagraphRenderBox box) {
286: rawResult = box.getRawValue();
287: paragraphBounds.setRect(box.getX(), box.getY(), box.getWidth(),
288: box.getHeight());
289: //final long y2 = box.getY() + box.getHeight();
290: contentAreaX1 = box.getContentAreaX1();
291: contentAreaX2 = box.getContentAreaX2();
292:
293: RenderBox lineBox = (RenderBox) box.getFirstChild();
294: while (lineBox != null) {
295: manualBreak = false;
296: processTextLine(lineBox, contentAreaX1, contentAreaX2);
297: if (manualBreak) {
298: addLinebreak();
299: } else if (lineBox.getStaticBoxLayoutProperties()
300: .isPreserveSpace() == false
301: && lineBox.getNext() != null) {
302: addSoftBreak();
303: } else {
304: addEmptyBreak();
305: }
306: lineBox = (RenderBox) lineBox.getNext();
307: }
308: }
309:
310: protected void addEmptyBreak() {
311: text.append(" ");
312: }
313:
314: protected void addSoftBreak() {
315: text.append(" ");
316: }
317:
318: protected void addLinebreak() {
319: text.append("\n");
320: }
321:
322: protected void processTextLine(final RenderBox lineBox,
323: final long contentAreaX1, final long contentAreaX2) {
324: if (lineBox.isNodeVisible(paragraphBounds) == false) {
325: return;
326: }
327: ellipseDrawn = false;
328:
329: this .textLineOverflow = ((lineBox.getX() + lineBox.getWidth()) > contentAreaX2)
330: && lineBox.getStyleSheet().getBooleanStyleProperty(
331: ElementStyleKeys.OVERFLOW_X, false) == false;
332:
333: if (textLineOverflow) {
334: revalidateTextEllipseProcessStep.compute(lineBox,
335: contentAreaX1, contentAreaX2);
336: }
337:
338: startProcessing(lineBox);
339: }
340:
341: public Object getRawResult() {
342: return rawResult;
343: }
344:
345: protected void setRawResult(final Object rawResult) {
346: this .rawResult = rawResult;
347: }
348:
349: public String getText() {
350: return text.toString();
351: }
352:
353: public int getTextLength() {
354: return text.length();
355: }
356:
357: protected void clearText() {
358: text.delete(0, text.length());
359: }
360:
361: protected StrictBounds getParagraphBounds() {
362: return paragraphBounds;
363: }
364:
365: public boolean isTextLineOverflow() {
366: return textLineOverflow;
367: }
368: }
|