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: * PdfLogicalPageDrawable.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.output.pageable.pdf.internal;
030:
031: import java.awt.Color;
032: import java.awt.geom.AffineTransform;
033:
034: import com.lowagie.text.pdf.BaseFont;
035: import com.lowagie.text.pdf.PdfAction;
036: import com.lowagie.text.pdf.PdfContentByte;
037: import com.lowagie.text.pdf.PdfDestination;
038: import com.lowagie.text.pdf.PdfName;
039: import com.lowagie.text.pdf.PdfOutline;
040: import com.lowagie.text.pdf.PdfString;
041: import com.lowagie.text.pdf.PdfTextArray;
042: import com.lowagie.text.pdf.PdfWriter;
043: import org.jfree.fonts.itext.BaseFontRecord;
044: import org.jfree.fonts.itext.BaseFontSupport;
045: import org.jfree.fonts.text.Spacing;
046: import org.jfree.report.layout.model.InlineRenderBox;
047: import org.jfree.report.layout.model.LogicalPageBox;
048: import org.jfree.report.layout.model.PhysicalPageBox;
049: import org.jfree.report.layout.model.RenderNode;
050: import org.jfree.report.layout.model.RenderableText;
051: import org.jfree.report.layout.output.OutputProcessorFeature;
052: import org.jfree.report.layout.output.OutputProcessorMetaData;
053: import org.jfree.report.layout.text.Glyph;
054: import org.jfree.report.modules.output.pageable.graphics.internal.LogicalPageDrawable;
055: import org.jfree.report.style.ElementStyleKeys;
056: import org.jfree.report.style.StyleSheet;
057: import org.jfree.report.style.TextStyleKeys;
058: import org.jfree.report.util.geom.StrictGeomUtility;
059:
060: /**
061: * Creation-Date: 17.07.2007, 18:41:46
062: *
063: * @author Thomas Morgner
064: */
065: public class PdfLogicalPageDrawable extends LogicalPageDrawable {
066: protected static class PdfTextSpec extends TextSpec {
067: private boolean embedded;
068: private String encoding;
069: private BaseFontRecord baseFontRecord;
070: private BaseFontRecord baseFont;
071: private PdfContentByte contentByte;
072:
073: public PdfTextSpec(final StyleSheet layoutContext,
074: final OutputProcessorMetaData metaData,
075: final PdfGraphics2D g2,
076: final BaseFontSupport fontSupport,
077: final BaseFontRecord baseFont, final PdfContentByte cb) {
078: super (layoutContext, metaData, g2);
079: this .baseFont = baseFont;
080: contentByte = cb;
081: encoding = (String) layoutContext.getStyleProperty(
082: TextStyleKeys.FONTENCODING, fontSupport
083: .getDefaultEncoding());
084:
085: embedded = metaData
086: .isFeatureSupported(OutputProcessorFeature.EMBED_ALL_FONTS)
087: || layoutContext
088: .getBooleanStyleProperty(TextStyleKeys.EMBEDDED_FONT);
089:
090: baseFontRecord = fontSupport.createBaseFontRecord(
091: getFontName(), isBold(), isItalics(), encoding,
092: embedded);
093: }
094:
095: public BaseFontRecord getBaseFont() {
096: return baseFont;
097: }
098:
099: public PdfContentByte getContentByte() {
100: return contentByte;
101: }
102:
103: public boolean isEmbedded() {
104: return embedded;
105: }
106:
107: public String getEncoding() {
108: return encoding;
109: }
110:
111: public BaseFontRecord getBaseFontRecord() {
112: return baseFontRecord;
113: }
114:
115: public void finishText() {
116: contentByte.endText();
117: }
118:
119: public void close() {
120: //super.close(); // we do not dispose the graphics as we are working on the original object
121: }
122: }
123:
124: private static final float ITALIC_ANGLE = 0.21256f;
125:
126: private PdfWriter writer;
127: private BaseFontSupport fontSupport;
128: private float globalHeight;
129:
130: public PdfLogicalPageDrawable(final LogicalPageBox rootBox,
131: final OutputProcessorMetaData metaData,
132: final PdfWriter writer, final PhysicalPageBox page,
133: final BaseFontSupport fontSupport) {
134: super (rootBox, metaData);
135: this .writer = writer;
136: this .fontSupport = fontSupport;
137: if (page != null) {
138: this .globalHeight = (float) StrictGeomUtility
139: .toExternalValue(page.getHeight()
140: - page.getImageableY());
141: } else {
142: this .globalHeight = rootBox.getPageHeight();
143: }
144: }
145:
146: protected void drawAnchor(final RenderNode content) {
147: if (content.isNodeVisible(getDrawArea()) == false) {
148: return;
149: }
150: final String anchorName = (String) content.getStyleSheet()
151: .getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
152: if (anchorName == null) {
153: return;
154: }
155: final AffineTransform affineTransform = getGraphics()
156: .getTransform();
157: final float translateX = (float) affineTransform
158: .getTranslateX();
159:
160: final float upperY = translateX
161: + (float) (globalHeight - StrictGeomUtility
162: .toExternalValue(content.getY()));
163: final float leftX = (float) (StrictGeomUtility
164: .toExternalValue(content.getX()));
165: final PdfDestination dest = new PdfDestination(
166: PdfDestination.FIT, leftX, upperY, 0);
167: writer.getDirectContent().localDestination(anchorName, dest);
168: }
169:
170: protected void drawBookmark(final RenderNode box,
171: final String bookmark) {
172: if (box.isNodeVisible(getDrawArea()) == false) {
173: return;
174: }
175: final PdfOutline root = writer.getDirectContent()
176: .getRootOutline();
177:
178: final AffineTransform affineTransform = getGraphics()
179: .getTransform();
180: final float translateX = (float) affineTransform
181: .getTranslateX();
182:
183: final float upperY = translateX
184: + (float) (globalHeight - StrictGeomUtility
185: .toExternalValue(box.getY()));
186: final float leftX = (float) (StrictGeomUtility
187: .toExternalValue(box.getX()));
188: final PdfDestination dest = new PdfDestination(
189: PdfDestination.FIT, leftX, upperY, 0);
190: new PdfOutline(root, dest, bookmark);
191: // destination will always point to the 'current' page
192: // todo: Make this a hierarchy ..
193: }
194:
195: protected void drawHyperlink(final RenderNode box,
196: final String target, final String window) {
197: if (box.isNodeVisible(getDrawArea()) == false) {
198: return;
199: }
200:
201: final AffineTransform affineTransform = getGraphics()
202: .getTransform();
203: final float translateX = (float) affineTransform
204: .getTranslateX();
205:
206: final float leftX = translateX
207: + (float) (StrictGeomUtility
208: .toExternalValue(box.getX()));
209: final float rightX = translateX
210: + (float) (StrictGeomUtility.toExternalValue(box.getX()
211: + box.getWidth()));
212: final float lowerY = (float) (globalHeight - StrictGeomUtility
213: .toExternalValue(box.getY() + box.getHeight()));
214: final float upperY = (float) (globalHeight - StrictGeomUtility
215: .toExternalValue(box.getY()));
216: final PdfContentByte cb = this .writer.getDirectContent();
217:
218: if (target.length() > 0 && target.charAt(0) == '#'
219: && "_self".equalsIgnoreCase(window)) {
220: // A local goto.
221: cb.localGoto(target, leftX, lowerY, rightX, upperY);
222: return;
223: }
224:
225: final PdfAction action = new PdfAction();
226: action.put(PdfName.S, PdfName.URI);
227: action.put(PdfName.URI, new PdfString(target));
228: cb.setAction(action, leftX, lowerY, rightX, upperY);
229: // // Title support requires us to add a annotation and to selectively show or hide it
230: // // This will be a 1.0 feature, there is not enough time to do it properly now.
231: // final PdfAction hideAction = new PdfAction();
232: // action.put (PdfName.S, PdfName.HIDE);
233: // action.put (PdfName.T, <dictionary-reference>);
234: // cb.setAction(hideAction, leftX, lowerY, rightX, upperY);
235:
236: }
237:
238: protected void drawText(final RenderableText renderableText,
239: final long contentX2) {
240: if (renderableText.getLength() == 0) {
241: return;
242: }
243: if (renderableText.isNodeVisible(getDrawArea()) == false) {
244: return;
245: }
246:
247: final long posX = renderableText.getX();
248: final long posY = renderableText.getY();
249: final float x1 = (float) (StrictGeomUtility
250: .toExternalValue(posX));
251:
252: final PdfContentByte cb;
253: PdfTextSpec textSpec = (PdfTextSpec) getTextSpec();
254: if (textSpec == null) {
255: final StyleSheet layoutContext = renderableText
256: .getStyleSheet();
257:
258: // The code below may be weird, but at least it is predictable weird.
259: final String fontName = getMetaData()
260: .getNormalizedFontFamilyName(
261: (String) layoutContext
262: .getStyleProperty(TextStyleKeys.FONT));
263: final String encoding = (String) layoutContext
264: .getStyleProperty(TextStyleKeys.FONTENCODING,
265: fontSupport.getDefaultEncoding());
266: final float fontSize = (float) layoutContext
267: .getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 10);
268:
269: final boolean embed = getMetaData().isFeatureSupported(
270: OutputProcessorFeature.EMBED_ALL_FONTS)
271: || layoutContext
272: .getBooleanStyleProperty(TextStyleKeys.EMBEDDED_FONT);
273: final boolean bold = layoutContext
274: .getBooleanStyleProperty(TextStyleKeys.BOLD);
275: final boolean italics = layoutContext
276: .getBooleanStyleProperty(TextStyleKeys.ITALIC);
277:
278: final BaseFontRecord baseFontRecord = fontSupport
279: .createBaseFontRecord(fontName, bold, italics,
280: encoding, embed);
281:
282: final PdfGraphics2D g2 = (PdfGraphics2D) getGraphics();
283: final Color cssColor = (Color) layoutContext
284: .getStyleProperty(ElementStyleKeys.PAINT);
285: g2.setPaint(cssColor);
286: g2.setFillPaint();
287: g2.setStrokePaint();
288: //final float translateY = (float) affineTransform.getTranslateY();
289:
290: cb = g2.getRawContentByte();
291:
292: textSpec = new PdfTextSpec(layoutContext, getMetaData(),
293: g2, fontSupport, baseFontRecord, cb);
294: setTextSpec(textSpec);
295:
296: cb.beginText();
297: cb.setFontAndSize(baseFontRecord.getBaseFont(), fontSize);
298: } else {
299: cb = textSpec.getContentByte();
300: }
301:
302: final BaseFontRecord baseFontRecord = textSpec.getBaseFont();
303: final BaseFont baseFont = baseFontRecord.getBaseFont();
304: final float ascent = baseFont.getFontDescriptor(
305: BaseFont.BBOXURY, textSpec.getFontSize());
306: final float y2 = (float) (StrictGeomUtility
307: .toExternalValue(posY) + ascent);
308: final float y = globalHeight - y2;
309:
310: final AffineTransform affineTransform = textSpec.getGraphics()
311: .getTransform();
312: final float translateX = (float) affineTransform
313: .getTranslateX();
314:
315: if (baseFontRecord.isTrueTypeFont() && textSpec.isBold()) {
316: final float strokeWidth = textSpec.getFontSize() / 30.0f; // right from iText ...
317: if (strokeWidth == 1) {
318: cb
319: .setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
320: } else {
321: cb
322: .setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE);
323: cb.setLineWidth(strokeWidth);
324: }
325: } else {
326: cb
327: .setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
328: }
329:
330: // if the font does not declare to be italics already, emulate it ..
331: if (baseFontRecord.isTrueTypeFont() && textSpec.isItalics()) {
332: final float italicAngle = baseFont.getFontDescriptor(
333: BaseFont.ITALICANGLE, textSpec.getFontSize());
334: if (italicAngle == 0) {
335: // italics requested, but the font itself does not supply italics gylphs.
336: cb.setTextMatrix(1, 0, ITALIC_ANGLE, 1,
337: x1 + translateX, y);
338: } else {
339: cb.setTextMatrix(x1 + translateX, y);
340: }
341: } else {
342: cb.setTextMatrix(x1 + translateX, y);
343: }
344:
345: final OutputProcessorMetaData metaData = getMetaData();
346: final Glyph[] gs = renderableText.getGlyphs();
347: final int offset = renderableText.getOffset();
348:
349: if (metaData
350: .isFeatureSupported(OutputProcessorFeature.FAST_FONTRENDERING)
351: && isNormalTextSpacing(renderableText)) {
352: final int maxLength = computeMaximumTextSize(
353: renderableText, contentX2);
354: final String text = textToString(gs, renderableText
355: .getOffset(), maxLength);
356:
357: cb.showText(text);
358: } else {
359: final PdfTextArray textArray = new PdfTextArray();
360: final StringBuffer buffer = new StringBuffer();
361: final int maxPos = offset
362: + computeMaximumTextSize(renderableText, contentX2);
363:
364: for (int i = offset; i < maxPos; i++) {
365: final Glyph g = gs[i];
366: final Spacing spacing = g.getSpacing();
367: if (i != offset) {
368: final int optimum = spacing.getOptimum();
369: if (optimum != 0) {
370: textArray.add(buffer.toString());
371: textArray
372: .add(-optimum / textSpec.getFontSize());
373: buffer.setLength(0);
374: }
375: }
376:
377: final String text = glpyhToString(g);
378: buffer.append(text);
379: }
380: if (buffer.length() > 0) {
381: textArray.add(buffer.toString());
382: }
383: cb.showText(textArray);
384: }
385: }
386:
387: protected boolean startInlineBox(final InlineRenderBox box) {
388: final PdfTextSpec textSpec = (PdfTextSpec) getTextSpec();
389: if (textSpec != null) {
390: textSpec.finishText();
391: }
392: return super .startInlineBox(box);
393: }
394:
395: protected void finishInlineBox(final InlineRenderBox box) {
396: final PdfTextSpec textSpec = (PdfTextSpec) getTextSpec();
397: if (textSpec != null) {
398: textSpec.finishText();
399: }
400: super.finishInlineBox(box);
401: }
402: }
|