001: package jimm.datavision.layout.pdf;
002:
003: import jimm.datavision.*;
004: import jimm.datavision.field.*;
005: import jimm.datavision.layout.LayoutEngine;
006: import jimm.datavision.layout.LineDrawer;
007: import jimm.util.StringUtils;
008: import java.io.OutputStream;
009: import java.util.*;
010: import com.lowagie.text.*;
011: import com.lowagie.text.pdf.*;
012:
013: /**
014: * A PDF layout engine.
015: *
016: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
017: */
018: public class PDFLE extends LayoutEngine implements LineDrawer {
019:
020: protected OutputStream outStream;
021: protected Document doc;
022: protected PdfContentByte content;
023: protected HashMap baseFonts;
024: protected double prevThickness;
025:
026: //
027: // The following code are modified to handle CJK fonts correctly
028: //
029: protected static HashMap cjkFontEncodingMap = new HashMap();
030: static {
031: cjkFontEncodingMap.put("STSong-Light", new String[] {
032: "UniGB-UCS2-H", "UniGB-UCS2-V" });
033: cjkFontEncodingMap.put("STSongStd-Light", new String[] {
034: "UniGB-UCS2-H", "UniGB-UCS2-V" });
035:
036: cjkFontEncodingMap.put("MHei-Medium", new String[] {
037: "UniCNS-UCS2-H", "UniCNS-UCS2-V" });
038: cjkFontEncodingMap.put("MSung-Light", new String[] {
039: "UniCNS-UCS2-H", "UniCNS-UCS2-V" });
040: cjkFontEncodingMap.put("MSungStd-Light", new String[] {
041: "UniCNS-UCS2-H", "UniCNS-UCS2-V" });
042:
043: cjkFontEncodingMap.put("HeiseiMin-W3", new String[] {
044: "UniJIS-UCS2-H", "UniJIS-UCS2-V", "UniJIS-UCS2-HW-H",
045: "UniJIS-UCS2-HW-V" });
046: cjkFontEncodingMap.put("HeiseiKakuGo-W5", new String[] {
047: "UniJIS-UCS2-H", "UniJIS-UCS2-V", "UniJIS-UCS2-HW-H",
048: "UniJIS-UCS2-HW-V" });
049: cjkFontEncodingMap.put("KozMinPro-Regular", new String[] {
050: "UniJIS-UCS2-H", "UniJIS-UCS2-V", "UniJIS-UCS2-HW-H",
051: "UniJIS-UCS2-HW-V" });
052:
053: cjkFontEncodingMap.put("HYGoThic-Medium", new String[] {
054: "UniKS-UCS2-H", "UniKS-UCS2-V" });
055: cjkFontEncodingMap.put("HYSMyeongJo-Medium", new String[] {
056: "UniKS-UCS2-H", "UniKS-UCS2-V" });
057: cjkFontEncodingMap.put("HYSMyeongJoStd", new String[] {
058: "UniKS-UCS2-H", "UniKS-UCS2-V" });
059: }
060:
061: ////////////////////////////////////////////////////////////////
062:
063: public PDFLE(OutputStream out) {
064: super (null);
065: outStream = out;
066: }
067:
068: /**
069: * Outputs the beginning of the document.
070: */
071: protected void doStart() {
072: baseFonts = new HashMap();
073:
074: PaperFormat fmt = report.getPaperFormat();
075: doc = new Document(new com.lowagie.text.Rectangle(0, 0,
076: (int) fmt.getWidth(), (int) fmt.getHeight()),
077: (float) fmt.getImageableX(),
078: (float) (fmt.getWidth() + fmt.getImageableX()),
079: (float) fmt.getImageableY(),
080: (float) (fmt.getHeight() + fmt.getImageableY()));
081:
082: PdfWriter writer = null;
083: try {
084: writer = PdfWriter.getInstance(doc, outStream);
085: baseFonts.put("Helvetica", BaseFont.createFont("Helvetica",
086: BaseFont.CP1252, BaseFont.NOT_EMBEDDED));
087: } catch (DocumentException e) {
088: ErrorHandler.error(e);
089: wantsMoreData = false; // Stop!
090: return;
091: } catch (java.io.IOException ioe) {
092: ErrorHandler.error(ioe);
093: wantsMoreData = false; // Stop!
094: return;
095: }
096:
097: String str = null;
098: if ((str = report.getTitle()) != null)
099: doc.addTitle(str);
100: if ((str = report.getAuthor()) != null)
101: doc.addAuthor(str);
102: doc.addCreator("DataVision version " + info.Version + " <"
103: + info.URL + ">");
104: doc.addCreationDate();
105:
106: doc.open();
107:
108: content = writer.getDirectContent();
109: }
110:
111: protected void doEnd() {
112: doc.close();
113: }
114:
115: protected void doStartPage() {
116: try {
117: prevThickness = 0;
118: doc.newPage();
119: }
120: // I don't quite get this... looking at the iText API, DocumentException can
121: // be thrown by the call to newPage(), but trying to catch that here
122: // results in an "exception never thrown" compile error. I didn't think the
123: // best idea was to remove the try...catch entirely, so it's now catching
124: // Exception, which should at least not break anything, but I don't like
125: // catching Exception, it's a code smell, so if anyone ever figures out
126: // why this is happening (it started with the upgrade to iText 2.0.1 by
127: // the way), I'd love to hear the reason!
128: catch (Exception e) {
129: ErrorHandler.error(e);
130: wantsMoreData = false; // Stop!
131: }
132: }
133:
134: /**
135: * Outputs a field.
136: *
137: * @param field the field to output
138: */
139: protected void doOutputField(Field field) {
140: String fieldAsString = field.toString();
141: if (fieldAsString == null || fieldAsString.length() == 0) {
142: makeBorders(field);
143: return;
144: }
145:
146: Format format = field.getFormat();
147: BaseFont baseFont = getFontForFormat(format);
148: float fontSize = (float) format.getSize();
149:
150: jimm.datavision.Point bottomLeft = bottomLeftOfField(field,
151: format.getSize(), baseFont);
152:
153: int align;
154: switch (format.getAlign()) {
155: case Format.ALIGN_CENTER:
156: align = PdfContentByte.ALIGN_CENTER;
157: bottomLeft.x += field.getBounds().width / 2; // x is center of bounds
158: break;
159: case Format.ALIGN_RIGHT:
160: align = PdfContentByte.ALIGN_RIGHT;
161: bottomLeft.x += field.getBounds().width; // x is right side of bounds
162: break;
163: case Format.ALIGN_LEFT: // fall through
164: default:
165: align = PdfContentByte.ALIGN_LEFT;
166: break;
167: }
168:
169: content.beginText();
170: content.setFontAndSize(baseFont, fontSize);
171: content.setColorFill(format.getColor());
172:
173: java.util.List lines = StringUtils
174: .splitIntoLines(fieldAsString);
175: double lineHeight = field.getOutputHeight() / lines.size();
176: for (Iterator iter = lines.iterator(); iter.hasNext();) {
177: String line = (String) iter.next();
178: content.showTextAligned(align, line, (float) bottomLeft.x,
179: (float) bottomLeft.y, 0f);
180: bottomLeft.y -= lineHeight;
181: }
182:
183: content.endText();
184:
185: // Borders
186: makeBorders(field);
187: }
188:
189: protected jimm.datavision.Point bottomLeftOfField(Field f,
190: double size, BaseFont baseFont) {
191: jimm.datavision.field.Rectangle r = f.getBounds();
192: jimm.datavision.Point bottomLeft = new jimm.datavision.Point(
193: r.x, r.y);
194:
195: // Translate to PDF coordinates, subtract (negative) font descent, and
196: // reflect vertically
197: translateToPDFCoords(bottomLeft);
198: bottomLeft.y -= baseFont.getFontDescriptor(BaseFont.DESCENT,
199: (float) size)
200: + r.height;
201:
202: return bottomLeft;
203: }
204:
205: protected void translateToPDFCoords(jimm.datavision.Point p) {
206: // Avoid setter methods; no one is observing this point
207: if (currentSection.getArea().getArea() != SectionArea.PAGE_FOOTER)
208: p.y = (pageHeight() - pageHeightUsed) - p.y;
209: else
210: p.y = currentSection.getOutputHeight() - p.y;
211: }
212:
213: //
214: // The following code are modified to handle CJK fonts correctly
215: //
216: protected BaseFont getFontForFormat(Format f) {
217: String name = baseFontName(f.getFont());
218: BaseFont bf = (BaseFont) baseFonts.get(name);
219: //System.out.println(name);
220: //original code is difficult to handle CJK fonts, so I have to modify it
221: if (bf == null) {
222: //start guessing
223: try {
224: bf = BaseFont.createFont(name, BaseFont.CP1252,
225: BaseFont.NOT_EMBEDDED);
226: baseFonts.put(name, bf);
227: return bf;
228: } catch (Exception e1) {
229: if (cjkFontEncodingMap.containsKey(name)) {
230: //guessing in CJK fonts
231: String[] encodings = (String[]) (cjkFontEncodingMap
232: .get(name));
233: for (int i = 0; i < encodings.length; i++) {
234: try {
235: bf = BaseFont
236: .createFont(name, encodings[i],
237: BaseFont.NOT_EMBEDDED);
238: baseFonts.put(name, bf);
239: return bf;
240: } catch (Exception eCJK) {
241: }
242: }
243: //if all in vain
244: return (BaseFont) baseFonts.get("Helvetica");
245: } else {
246: //otherwise, we have no choice
247: return (BaseFont) baseFonts.get("Helvetica");
248: }
249: }
250: }
251: return bf;
252: /*BaseFont bf = (BaseFont)baseFonts.get(name);
253: if (bf == null) {
254: try {
255: bf = BaseFont.createFont(name, BaseFont.CP1252,
256: BaseFont.NOT_EMBEDDED);
257: baseFonts.put(name, bf);
258: }
259: catch (Exception e) { // DocumentException or IOException
260: ErrorHandler.error(e);
261: bf = (BaseFont)baseFonts.get("Helvetica");
262: }
263: }
264: return bf;*/
265: }
266:
267: protected String baseFontName(java.awt.Font font) {
268: String family = font.getFamily().toLowerCase();
269: if (family.startsWith("courier")
270: || family.startsWith("monospace"))
271: return "Courier" + fontAttributes(font, "Bold", "Oblique");
272: else if (family.startsWith("helvetica")
273: || family.startsWith("sansserif"))
274: return "Helvetica"
275: + fontAttributes(font, "Bold", "Oblique");
276: else if (family.startsWith("symbol"))
277: return "Symbol";
278: else if (family.startsWith("zapfdingbats"))
279: return "ZapfDingbats";
280: else {
281: //if this is a known iText CJK font name, just return as is
282: if (cjkFontEncodingMap.containsKey(font.getName()))
283: return font.getName();
284: //otherwise, return a default setting
285: String fontAttrs = fontAttributes(font, "Bold", "Italic");
286: return "Times"
287: + (fontAttrs.length() > 0 ? fontAttrs : "-Roman");
288: //String fontAttrs = fontAttributes(font, "Bold", "Italic");
289: //return "Times" + (fontAttrs.length() > 0 ? fontAttrs : "-Roman");
290: }
291: }
292:
293: //////////////////////////////////////////////////////////////////////////////////
294: protected String fontAttributes(java.awt.Font font, String bold,
295: String italic) {
296: if (font.isBold() && font.isItalic())
297: return "-" + bold + italic;
298: else if (font.isBold())
299: return "-" + bold;
300: else if (font.isItalic())
301: return "-" + italic;
302: else
303: return "";
304: }
305:
306: /**
307: * Ignores image output
308: *
309: * @param field an image field
310: */
311: protected void doOutputImage(ImageField field) {
312: try {
313: Image img = Image.getInstance(field.getImageURL());
314:
315: // Translate to PDF coordinates and reflect vertically
316: jimm.datavision.field.Rectangle r = field.getBounds();
317: jimm.datavision.Point p = new jimm.datavision.Point(r.x,
318: r.y);
319: translateToPDFCoords(p);
320: p.y -= field.getOutputHeight();
321:
322: img.setAbsolutePosition((float) p.x, (float) p.y);
323: content.addImage(img);
324: } catch (Exception e) { // DocumentException or MalformedURLException
325: wantsMoreData = false;
326: ErrorHandler.error(e);
327: }
328: }
329:
330: /**
331: * Outputs a line. Calls {@link #drawLine}.
332: *
333: * @param line a line
334: */
335: protected void doOutputLine(Line line) {
336: drawLine(line, Boolean.TRUE);
337: }
338:
339: /**
340: * Outputs borders.
341: */
342: protected void makeBorders(Field field) {
343: field.getBorderOrDefault().eachLine(this , Boolean.FALSE);
344: content.stroke();
345: }
346:
347: /**
348: * Draw a single line.
349: *
350: * @param line a line
351: */
352: public void drawLine(Line line, Object arg) {
353: if (line.getThickness() != prevThickness) {
354: prevThickness = line.getThickness();
355: content.setLineWidth((float) prevThickness);
356: }
357: jimm.datavision.Point p0 = new jimm.datavision.Point(line
358: .getPoint(0));
359: jimm.datavision.Point p1 = new jimm.datavision.Point(line
360: .getPoint(1));
361: translateToPDFCoords(p0);
362: translateToPDFCoords(p1);
363: content.moveTo((float) p0.x, (float) p0.y);
364: content.lineTo((float) p1.x, (float) p1.y);
365: if (arg != Boolean.FALSE) // Yes "!=" instead of "equals"
366: content.stroke();
367: }
368:
369: }
|