Java tutorial
/* SpagoBI, the Open Source Business Intelligence suite * Copyright (C) 2012 Engineering Ingegneria Informatica S.p.A. - SpagoBI Competency Center * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0, without the "Incompatible With Secondary Licenses" notice. * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package it.eng.spagobi.engines.worksheet.exporter; import it.eng.spagobi.engines.qbe.QbeEngineConfig; import it.eng.spagobi.engines.qbe.crosstable.exporter.CrosstabPDFExporter; import it.eng.spagobi.engines.worksheet.services.export.ExportWorksheetAction; import it.eng.spagobi.tools.dataset.common.datastore.IDataStore; import it.eng.spagobi.utilities.engines.SpagoBIEngineRuntimeException; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.text.DecimalFormat; import java.util.ArrayList; import javax.imageio.ImageIO; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.JPEGTranscoder; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONObject; import com.lowagie.text.BadElementException; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Image; import com.lowagie.text.PageSize; import com.lowagie.text.Phrase; import com.lowagie.text.html.simpleparser.HTMLWorker; import com.lowagie.text.html.simpleparser.StyleSheet; import com.lowagie.text.pdf.ColumnText; import com.lowagie.text.pdf.PdfContentByte; import com.lowagie.text.pdf.PdfPageEventHelper; import com.lowagie.text.pdf.PdfWriter; /** * * @author Davide Zerbetto (davide.zerbetto@eng.it) */ public class WorkSheetPDFExporter { private Document pdfDocument = null; private PdfWriter docWriter = null; private IDataStore dataStore = null; private JSONObject currentSheetConf = null; private static float IMAGE_MAX_WIDTH = 250; private static float IMAGE_MAX_HEIGHT = 100; private static float TITLE_MAX_HEIGHT = 50; private static float MARGIN_TOP = 36; private static float MARGIN_RIGHT = 36; private static float MARGIN_BOTTOM = 36; private static float MARGIN_LEFT = 36; public static final String HEADER = "HEADER"; public static final String FOOTER = "FOOTER"; public static final String CONTENT = "CONTENT"; public static final String SHEET_TYPE = "SHEET_TYPE"; public static final String CHART = "CHART"; public static final String CROSSTAB = "CROSSTAB"; public static final String TABLE = "TABLE"; public static final String EMPTY = "EMPTY"; public static final String POSITION = "position"; public static final String TITLE = "title"; public static final String IMG = "img"; public static final String CENTER = "center"; public static final String RIGHT = "right"; public static final String LEFT = "left"; public static final String SVG = "SVG"; private DecimalFormat numberFormat; private String userDateFormat; /** Logger component. */ public static transient Logger logger = Logger.getLogger(WorkSheetPDFExporter.class); public void open(OutputStream outputStream) throws DocumentException { pdfDocument = new Document(PageSize.A4.rotate()); docWriter = PdfWriter.getInstance(pdfDocument, outputStream); docWriter.setPageEvent(new MyHeaderFooter()); pdfDocument.open(); } public void close() { pdfDocument.close(); docWriter.close(); } public void addSheet(JSONObject sheetJSON, IDataStore dataStore) { this.dataStore = dataStore; addSheet(sheetJSON); } public void addSheet(JSONObject sheetJSON) { try { float[] margins = getContentMargins(sheetJSON); setCurrentSheetConf(sheetJSON); // new margins will be applied on next page pdfDocument.setMargins(MARGIN_LEFT, MARGIN_RIGHT, margins[0], margins[1]); pdfDocument.newPage(); JSONObject content = sheetJSON.getJSONObject(WorkSheetPDFExporter.CONTENT); String sheetType = content.getString(WorkSheetPDFExporter.SHEET_TYPE); if (WorkSheetPDFExporter.CHART.equalsIgnoreCase(sheetType)) { addChart(content, margins); } else if (WorkSheetPDFExporter.TABLE.equalsIgnoreCase(sheetType)) { addTable(content); } else if (WorkSheetPDFExporter.CROSSTAB.equalsIgnoreCase(sheetType)) { addCrosstab(content); } //if the content is hidden else if (WorkSheetPDFExporter.EMPTY.equalsIgnoreCase(sheetType)) { Phrase emptyString = new Phrase(" "); pdfDocument.add(emptyString); } else { logger.error("Sheet type " + sheetType + " not recognized"); } pdfDocument.newPage(); // finalize page (necessary for MyHeaderFooter onEndPage trigger) } catch (Exception e) { throw new RuntimeException("Error while adding sheet", e); } } private float[] getContentMargins(JSONObject sheetJSON) throws Exception { float top = 0; float bottom = 0; if (sheetJSON.has(WorkSheetPDFExporter.HEADER)) { top = getTopMargin(sheetJSON.getJSONObject(WorkSheetPDFExporter.HEADER)); } if (sheetJSON.has(WorkSheetPDFExporter.FOOTER)) { bottom = getBottomMargin(sheetJSON.getJSONObject(WorkSheetPDFExporter.FOOTER)); } float[] toReturn = new float[] { top, bottom }; return toReturn; } private float getBottomMargin(JSONObject footer) throws Exception { return getMargin(footer) + MARGIN_BOTTOM; } private float getTopMargin(JSONObject header) throws Exception { return getMargin(header) + MARGIN_TOP; } private float getMargin(JSONObject headerOrFooter) throws Exception { float toReturn = 0; String title = headerOrFooter.optString(TITLE); String imgName = headerOrFooter.optString(IMG); String imagePosition = headerOrFooter.optString(POSITION); if (title != null && !title.trim().equals("")) { toReturn = TITLE_MAX_HEIGHT; } if (imgName != null && !imgName.equals("") && !imgName.equals("null")) { if (CENTER.equals(imagePosition)) { toReturn += IMAGE_MAX_HEIGHT; } else { toReturn = IMAGE_MAX_HEIGHT; } } return toReturn; } private void setHeader(JSONObject header) { try { String title = header.optString(TITLE); String imgName = header.optString(IMG); String imagePosition = header.optString(POSITION); Image image = null; if (imgName != null && !imgName.equals("") && !imgName.equals("null")) { File imageFile = getImage(imgName); if (!imageFile.exists() || !imageFile.isFile()) { logger.error("Image " + imgName + " not found!!!"); } else { image = Image.getInstance(imageFile.getPath()); fitImage(image, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); setHeaderImagePosition(image, imagePosition); pdfDocument.add(image); } } if (title != null && !title.trim().equals("") && !title.trim().equals("<br>")) { title = new String(title.getBytes("ISO-8859-1")); // workaround for encoding problem float[] titlePosition = getHeaderTitlePosition(image, imagePosition); addHtmlToPdfContentByte(title, titlePosition); } } catch (Exception e) { throw new RuntimeException("Error while adding header", e); } } private float[] getHeaderTitlePosition(Image image, String imagePosition) { float llx, lly; // lower-left corner x and y position float urx, ury; // upper-right corner x and y position float imageHeight = image != null ? image.getHeight() : 0; float imageWidth = image != null ? image.getWidth() : 0; if (LEFT.equals(imagePosition)) { llx = MARGIN_LEFT + imageWidth + 30; lly = PageSize.A4.getWidth() - (MARGIN_TOP + Math.max(imageHeight, IMAGE_MAX_HEIGHT)); urx = PageSize.A4.getHeight() - MARGIN_RIGHT; ury = PageSize.A4.getWidth() - MARGIN_TOP; } else if (RIGHT.equals(imagePosition)) { llx = MARGIN_LEFT; lly = PageSize.A4.getWidth() - (MARGIN_TOP + Math.max(imageHeight, IMAGE_MAX_HEIGHT)); urx = PageSize.A4.getHeight() - (MARGIN_RIGHT + imageWidth + 30); ury = PageSize.A4.getWidth() - MARGIN_TOP; } else { // CENTER case llx = MARGIN_LEFT; lly = PageSize.A4.getWidth() - (MARGIN_TOP + imageHeight + TITLE_MAX_HEIGHT); urx = PageSize.A4.getHeight() - MARGIN_RIGHT; ury = PageSize.A4.getWidth() - (MARGIN_TOP + imageHeight); } float[] toReturn = new float[] { llx, lly, urx, ury }; return toReturn; } public void addHtmlToPdfContentByte(String html, float[] pos) { PdfContentByte cb = docWriter.getDirectContent(); StyleSheet styles = createDefaultStyleSheet(); ColumnText ct = new ColumnText(cb); ct.setSimpleColumn(pos[0], pos[1], pos[2], pos[3]); ct.setYLine(pos[3]); try { ArrayList htmlObjs = HTMLWorker.parseToList(new StringReader(html), styles); for (int k = 0; k < htmlObjs.size(); ++k) { ct.addElement((Element) htmlObjs.get(k)); } ct.go(); } catch (Exception e) { throw new RuntimeException("Could not parse HTML", e); } } private StyleSheet createDefaultStyleSheet() { StyleSheet styles = new StyleSheet(); // styles.loadTagStyle("ul", "face", "Times"); // styles.loadTagStyle("ul", "size", "25px"); // styles.loadTagStyle("ul", "leading", "15f"); // styles.loadTagStyle("ul", "list-style-type", "square"); // styles.loadTagStyle("li", "face", "Times"); // styles.loadTagStyle("li", "size", "25px"); // styles.loadTagStyle("li", "leading", "15f"); // styles.loadTagStyle("p", "face", "Times"); // styles.loadTagStyle("p", "size", "11px"); // styles.loadTagStyle("p", "leading", "12f"); // styles.loadTagStyle("p", "spacingAfter", "6x"); return styles; } private void setHeaderImagePosition(Image image, String imagePosition) { float top = PageSize.A4.getWidth() - (MARGIN_TOP + image.getHeight()); // remember that the page is A4 rotated float left = MARGIN_LEFT; if (LEFT.equals(imagePosition)) { left = MARGIN_LEFT; } else if (CENTER.equals(imagePosition)) { left = PageSize.A4.getHeight() / 2 - image.getWidth() / 2; } else if (RIGHT.equals(imagePosition)) { left = PageSize.A4.getHeight() - (MARGIN_RIGHT + image.getWidth()); // remember that the page is A4 rotated } image.setAbsolutePosition(left, top); } private void setFooterImagePosition(Image image, String imagePosition) { float top = MARGIN_BOTTOM; float left = MARGIN_LEFT; if (LEFT.equals(imagePosition)) { left = MARGIN_LEFT; } else if (CENTER.equals(imagePosition)) { left = PageSize.A4.getHeight() / 2 - image.getWidth() / 2; } else if (RIGHT.equals(imagePosition)) { left = PageSize.A4.getHeight() - (MARGIN_RIGHT + image.getWidth()); // remember that the page is A4 rotated } image.setAbsolutePosition(left, top); } private void setFooter(JSONObject footer) { try { String title = footer.optString(TITLE); String imgName = footer.optString(IMG); String imagePosition = footer.optString(POSITION); Image image = null; if (imgName != null && !imgName.equals("") && !imgName.equals("null")) { File imageFile = getImage(imgName); if (!imageFile.exists() || !imageFile.isFile()) { logger.error("Image " + imgName + " not found!!!"); } else { image = Image.getInstance(imageFile.getPath()); fitImage(image, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); setFooterImagePosition(image, imagePosition); pdfDocument.add(image); } } if (title != null && !title.trim().equals("") && !title.trim().equals("<br>")) { title = new String(title.getBytes("ISO-8859-1")); // workaround for encoding problem float[] titlePosition = getFooterTitlePosition(image, imagePosition); addHtmlToPdfContentByte(title, titlePosition); } } catch (Exception e) { throw new RuntimeException("Error while adding header", e); } } private float[] getFooterTitlePosition(Image image, String imagePosition) { float llx, lly; // lower-left corner x and y position float urx, ury; // upper-right corner x and y position float imageHeight = image != null ? image.getHeight() : 0; float imageWidth = image != null ? image.getWidth() : 0; if (LEFT.equals(imagePosition)) { llx = MARGIN_LEFT + imageWidth + 30; lly = MARGIN_BOTTOM; urx = PageSize.A4.getHeight() - MARGIN_RIGHT; ury = MARGIN_BOTTOM + Math.max(imageHeight, IMAGE_MAX_HEIGHT); } else if (RIGHT.equals(imagePosition)) { llx = MARGIN_LEFT; lly = MARGIN_BOTTOM; urx = PageSize.A4.getHeight() - (MARGIN_RIGHT + imageWidth + 30); ury = MARGIN_BOTTOM + Math.max(imageHeight, IMAGE_MAX_HEIGHT); } else { // CENTER case llx = MARGIN_LEFT; lly = MARGIN_BOTTOM + imageHeight; urx = PageSize.A4.getHeight() - MARGIN_RIGHT; ury = MARGIN_BOTTOM + imageHeight + TITLE_MAX_HEIGHT; } float[] toReturn = new float[] { llx, lly, urx, ury }; return toReturn; } private File getImage(String fileName) { logger.debug("IN"); File toReturn = null; File imagesDir = QbeEngineConfig.getInstance().getWorksheetImagesDir(); toReturn = new File(imagesDir, fileName); logger.debug("OUT"); return toReturn; } private void addChart(JSONObject content, float[] margins) { try { InputStream inputStream = null; OutputStream outputStream = null; File imageFile = null; String chartType = content.optString("CHART_TYPE"); //check If the chart to export is ext if (chartType != null && chartType.equals("ext3")) { JSONArray images = content.optJSONArray("CHARTS_ARRAY"); if (images == null || images.length() == 0) { return; } for (int i = 0; i < images.length(); i++) { inputStream = new ByteArrayInputStream( ExportWorksheetAction.decodeToByteArray(images.getString(i))); String ext = ".png"; BufferedImage image = ImageIO.read(inputStream); imageFile = File.createTempFile("chart", ext); ImageIO.write(image, "png", imageFile); addChart(imageFile, content, margins); } } else { String svg = content.getString(SVG); //Don't change ISO-8859-1 because it's the only way to export specific symbols inputStream = new ByteArrayInputStream(svg.getBytes("ISO-8859-1")); imageFile = File.createTempFile("chart", ".jpg"); outputStream = new FileOutputStream(imageFile); transformSVGIntoJPEG(inputStream, outputStream); addChart(imageFile, content, margins); } } catch (Exception e) { throw new RuntimeException("Error while adding chart", e); } } private void addChart(File imageFile, JSONObject content, float[] margins) throws MalformedURLException, IOException, DocumentException { Image jpg = Image.getInstance(imageFile.getPath()); float topMargin = margins[0]; float bottomMargin = margins[1]; float chartMaxHeight = PageSize.A4.getWidth() - (topMargin + bottomMargin); // remember that the page is A4 rotated float chartMaxWidth = PageSize.A4.getHeight() - (MARGIN_LEFT + MARGIN_RIGHT); // remember that the page is A4 rotated float[] newDimensions = fitImage(jpg, chartMaxWidth, chartMaxHeight); float positionX = (PageSize.A4.getHeight() - newDimensions[0]) / 2; float positionY = bottomMargin + (chartMaxHeight - newDimensions[1]) / 2; // center the image into the available height jpg.setAbsolutePosition(positionX, positionY); pdfDocument.add(jpg); } private void addTable(JSONObject content) { try { DataSourceTablePDFExporter tableExp = new DataSourceTablePDFExporter(dataStore, numberFormat, userDateFormat); tableExp.export(pdfDocument); } catch (Exception e) { throw new RuntimeException("Error while adding chart", e); } } private void addCrosstab(JSONObject content) { try { CrosstabPDFExporter csExporter = new CrosstabPDFExporter(); String crosstab = content.getString(WorkSheetXLSExporter.CROSSTAB); // TODO: calculate crosstab server-side if (crosstab != null && !crosstab.equals("null")) { JSONObject crosstabJSON = new JSONObject(crosstab); csExporter.export(crosstabJSON, pdfDocument, numberFormat); } } catch (Exception e) { throw new RuntimeException("Error while adding the crosstab", e); } } private float[] fitImage(Image jpg, float maxWidth, float maxHeight) { float newWidth = jpg.getWidth(); float newHeight = jpg.getHeight(); if (jpg.getWidth() > maxWidth) { newWidth = maxWidth; newHeight = (newWidth / jpg.getWidth()) * jpg.getHeight(); jpg.scalePercent(newWidth * 100 / jpg.getWidth()); } if (jpg.getHeight() > maxHeight) { newHeight = maxHeight; newWidth = (newHeight / jpg.getHeight()) * jpg.getWidth(); jpg.scalePercent(newHeight * 100 / jpg.getHeight()); } float[] toReturn = new float[] { newWidth, newHeight }; return toReturn; } public static void transformSVGIntoJPEG(InputStream inputStream, OutputStream outputStream) { // create a JPEG transcoder JPEGTranscoder t = new JPEGTranscoder(); // set the transcoding hints t.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(1)); t.addTranscodingHint(JPEGTranscoder.KEY_WIDTH, new Float(1000)); t.addTranscodingHint(JPEGTranscoder.KEY_ALLOWED_SCRIPT_TYPES, "*"); t.addTranscodingHint(JPEGTranscoder.KEY_CONSTRAIN_SCRIPT_ORIGIN, new Boolean(true)); t.addTranscodingHint(JPEGTranscoder.KEY_EXECUTE_ONLOAD, new Boolean(true)); // create the transcoder input Reader reader = new InputStreamReader(inputStream); TranscoderInput input = new TranscoderInput(reader); // create the transcoder output TranscoderOutput output = new TranscoderOutput(outputStream); // save the image try { t.transcode(input, output); } catch (TranscoderException e) { logger.error("Impossible to convert svg to jpeg: " + e.getCause(), e); throw new SpagoBIEngineRuntimeException("Impossible to convert svg to jpeg: " + e.getCause(), e); } } public void setNumberFormat(DecimalFormat numberFormat) { this.numberFormat = numberFormat; } public void setUserDateFormat(String userDateFormat) { this.userDateFormat = userDateFormat; } public JSONObject getCurrentSheetConf() { return currentSheetConf; } public void setCurrentSheetConf(JSONObject currentSheetConf) { this.currentSheetConf = currentSheetConf; } public class MyHeaderFooter extends PdfPageEventHelper { public MyHeaderFooter() { } @Override public void onEndPage(PdfWriter writer, Document document) { super.onEndPage(writer, document); addHeaderAndFooter(); } private void addHeaderAndFooter() { try { JSONObject sheetJSON = getCurrentSheetConf(); if (sheetJSON.has(WorkSheetPDFExporter.HEADER)) { JSONObject header = sheetJSON.getJSONObject(WorkSheetPDFExporter.HEADER); setHeader(header); } if (sheetJSON.has(WorkSheetPDFExporter.FOOTER)) { JSONObject footer = sheetJSON.getJSONObject(WorkSheetPDFExporter.FOOTER); setFooter(footer); } } catch (Exception e) { logger.error("Error while adding header or footer", e); } } } }