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: * ExcelPrinter.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.output.table.xls.helper;
030:
031: import java.awt.Graphics2D;
032: import java.awt.Image;
033: import java.awt.Shape;
034: import java.awt.image.BufferedImage;
035: import java.io.BufferedInputStream;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.io.OutputStream;
039: import java.net.URL;
040: import java.util.Date;
041: import java.util.HashMap;
042:
043: import org.apache.poi.hssf.usermodel.HSSFCell;
044: import org.apache.poi.hssf.usermodel.HSSFCellStyle;
045: import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
046: import org.apache.poi.hssf.usermodel.HSSFPatriarch;
047: import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
048: import org.apache.poi.hssf.usermodel.HSSFRichTextString;
049: import org.apache.poi.hssf.usermodel.HSSFRow;
050: import org.apache.poi.hssf.usermodel.HSSFSheet;
051: import org.apache.poi.hssf.usermodel.HSSFWorkbook;
052: import org.apache.poi.hssf.util.Region;
053: import org.apache.poi.poifs.filesystem.POIFSFileSystem;
054: import org.jfree.io.IOUtils;
055: import org.jfree.report.Anchor;
056: import org.jfree.report.ElementAlignment;
057: import org.jfree.report.ImageContainer;
058: import org.jfree.report.InvalidReportStateException;
059: import org.jfree.report.LocalImageContainer;
060: import org.jfree.report.URLImageContainer;
061: import org.jfree.report.DefaultImageReference;
062: import org.jfree.report.layout.model.LogicalPageBox;
063: import org.jfree.report.layout.model.PhysicalPageBox;
064: import org.jfree.report.layout.model.RenderBox;
065: import org.jfree.report.layout.model.RenderNode;
066: import org.jfree.report.layout.output.LogicalPageKey;
067: import org.jfree.report.layout.output.OutputProcessorMetaData;
068: import org.jfree.report.layout.output.RenderUtility;
069: import org.jfree.report.layout.output.OutputProcessorFeature;
070: import org.jfree.report.modules.output.table.base.SheetLayout;
071: import org.jfree.report.modules.output.table.base.TableCellDefinition;
072: import org.jfree.report.modules.output.table.base.TableContentProducer;
073: import org.jfree.report.modules.output.table.base.TableRectangle;
074: import org.jfree.report.modules.output.table.xls.ExcelTableModule;
075: import org.jfree.report.resourceloader.ImageFactory;
076: import org.jfree.report.style.ElementStyleKeys;
077: import org.jfree.report.style.StyleSheet;
078: import org.jfree.report.util.ImageUtils;
079: import org.jfree.report.util.IntegerCache;
080: import org.jfree.report.util.MemoryByteArrayOutputStream;
081: import org.jfree.report.util.geom.StrictBounds;
082: import org.jfree.report.util.geom.StrictGeomUtility;
083: import org.jfree.ui.Drawable;
084: import org.jfree.util.Configuration;
085: import org.jfree.util.Log;
086: import org.jfree.util.StringUtils;
087: import org.jfree.util.WaitingImageObserver;
088:
089: /**
090: * Creation-Date: 09.05.2007, 14:52:05
091: *
092: * @author Thomas Morgner
093: */
094: public class ExcelPrinter {
095: private InputStream templateInputStream;
096: private OutputStream outputStream;
097: private HSSFWorkbook workbook;
098: private HashMap sheetNamesCount;
099: private double scaleFactor;
100: private Configuration config;
101: private OutputProcessorMetaData metaData;
102: private HSSFSheet sheet;
103: private HSSFPatriarch patriarch;
104: private HSSFCellStyleProducer cellStyleProducer;
105:
106: public ExcelPrinter() {
107: this .sheetNamesCount = new HashMap();
108: }
109:
110: public void init(final Configuration config,
111: final OutputProcessorMetaData metaData,
112: final OutputStream outputStream) {
113: this .outputStream = outputStream;
114: this .config = config;
115: this .metaData = metaData;
116: try {
117: final String scaleFactorText = config
118: .getConfigProperty("org.jfree.report.modules.output.table.xls.CellWidthScaleFactor");
119: if (scaleFactorText == null) {
120: scaleFactor = 50;
121: } else {
122: scaleFactor = Double.parseDouble(scaleFactorText);
123: }
124: } catch (Exception e) {
125: this .scaleFactor = 50;
126: }
127: }
128:
129: public InputStream getTemplateInputStream() {
130: return templateInputStream;
131: }
132:
133: public void setTemplateInputStream(
134: final InputStream templateInputStream) {
135: this .templateInputStream = templateInputStream;
136: }
137:
138: private String makeUnique(final String name) {
139: final Integer count = (Integer) sheetNamesCount.get(name);
140: if (count == null) {
141: sheetNamesCount.put(name, IntegerCache.getInteger(1));
142: return name;
143: }
144:
145: final int value = count.intValue() + 1;
146: sheetNamesCount.put(name, IntegerCache.getInteger(value));
147: return makeUnique(name + ' ' + value);
148: }
149:
150: private boolean isValidSheetName(final String sheetname) {
151: if ((sheetname.indexOf('/') > -1)
152: || (sheetname.indexOf('\\') > -1)
153: || (sheetname.indexOf('?') > -1)
154: || (sheetname.indexOf('*') > -1)
155: || (sheetname.indexOf(']') > -1)
156: || (sheetname.indexOf('[') > -1)
157: || (sheetname.indexOf(':') > -1)) {
158: return false;
159: }
160:
161: return true;
162: }
163:
164: private HSSFCell getCellAt(final short x, final int y) {
165: final HSSFRow row = getRowAt(y);
166: final HSSFCell cell = row.getCell(x);
167: if (cell != null) {
168: return cell;
169: }
170: return row.createCell(x);
171: }
172:
173: private HSSFRow getRowAt(final int y) {
174: final HSSFRow row = sheet.getRow(y);
175: if (row != null) {
176: return row;
177: }
178: return sheet.createRow(y);
179: }
180:
181: public void print(final LogicalPageKey logicalPageKey,
182: final LogicalPageBox logicalPage,
183: final TableContentProducer contentProducer,
184: final boolean incremental) {
185: if (workbook == null) {
186: workbook = createWorkbook();
187:
188: final boolean hardLimit = "true"
189: .equals(config
190: .getConfigProperty("org.jfree.report.modules.output.table.xls.HardStyleCountLimit"));
191: cellStyleProducer = new HSSFCellStyleProducer(workbook,
192: hardLimit);
193: }
194:
195: if (sheet == null) {
196: sheet = openSheet(contentProducer.getSheetName());
197: // Start a new page.
198: final PhysicalPageBox page = logicalPage.getPageGrid()
199: .getPage(0, 0);
200: configureSheet(page);
201:
202: // Set column widths ..
203: final SheetLayout sheetLayout = contentProducer
204: .getSheetLayout();
205: final int columnCount = contentProducer.getColumnCount();
206: for (short col = 0; col < columnCount; col++) {
207: final double cellWidth = StrictGeomUtility
208: .toExternalValue(sheetLayout.getCellWidth(col,
209: col + 1));
210: final double poiCellWidth = (cellWidth * scaleFactor);
211: sheet.setColumnWidth(col, (short) poiCellWidth);
212: }
213:
214: // ... and row heights ..
215: final int rowCount = contentProducer.getRowCount();
216: for (int row = 0; row < rowCount; row += 1) {
217: final HSSFRow hssfRow = getRowAt(row);
218: final double lastRowHeight = StrictGeomUtility
219: .toExternalValue(sheetLayout.getRowHeight(row));
220: hssfRow.setHeightInPoints((float) (lastRowHeight));
221: }
222: }
223:
224: // and finally the content ..
225: final SheetLayout sheetLayout = contentProducer
226: .getSheetLayout();
227: final int colCount = sheetLayout.getColumnCount();
228: final int startRow = contentProducer.getFinishedRows();
229: final int finishRow = contentProducer.getFilledRows();
230: //Log.debug ("Excel: Processing: " + startRow + " " + finishRow + " " + incremental);
231:
232: for (int row = startRow; row < finishRow; row++) {
233: for (short col = 0; col < colCount; col++) {
234: final RenderBox content = contentProducer.getContent(
235: row, col);
236: final TableCellDefinition background = sheetLayout
237: .getBackgroundAt(row, col);
238:
239: if (content == null && background == null) {
240: if (row == 0 && col == 0) {
241: // create a single cell, so that we dont run into nullpointer inside POI..
242: getCellAt(col, row);
243: }
244: // An empty cell .. ignore
245: continue;
246: }
247: if (content == null) {
248: // A empty cell with a defined background ..
249: final HSSFCell cell = getCellAt(col, row);
250: final HSSFCellStyle style = cellStyleProducer
251: .createCellStyle(null, background);
252: if (style != null) {
253: cell.setCellStyle(style);
254: }
255: continue;
256: }
257:
258: if (content.isCommited() == false) {
259: throw new InvalidReportStateException(
260: "Uncommited content encountered");
261: }
262:
263: final long contentOffset = contentProducer
264: .getContentOffset(row, col);
265: final TableRectangle rectangle = sheetLayout
266: .getTableBounds(content.getX(), content.getY()
267: + contentOffset, content.getWidth(),
268: content.getHeight(), null);
269: if (rectangle.isOrigin(col, row) == false) {
270: // A spanned cell ..
271: continue;
272: }
273:
274: final TableCellDefinition realBackground;
275: if (background == null
276: || (rectangle.getColumnSpan() == 1 && rectangle
277: .getRowSpan() == 1)) {
278: realBackground = background;
279: } else {
280: realBackground = sheetLayout.getBackgroundAt(
281: rectangle.getX1(), rectangle.getY1(),
282: rectangle.getColumnSpan(), rectangle
283: .getRowSpan());
284: }
285: // export the cell and all content ..
286:
287: final HSSFCell cell = getCellAt(col, row);
288: final HSSFCellStyle style = cellStyleProducer
289: .createCellStyle(content, realBackground);
290: if (style != null) {
291: cell.setCellStyle(style);
292: }
293:
294: if (applyCellValue(metaData, content, cell,
295: sheetLayout, rectangle, contentOffset)) {
296: mergeCellRegion(rectangle, row, col, sheetLayout,
297: content);
298: }
299:
300: content.setFinished(true);
301: }
302:
303: }
304:
305: if (incremental == false) {
306: // cleanup ..
307: patriarch = null;
308: sheet = null;
309: }
310: }
311:
312: private void mergeCellRegion(final TableRectangle rectangle,
313: final int row, final short col,
314: final SheetLayout sheetLayout, final RenderBox content) {
315: final int rowSpan = rectangle.getRowSpan();
316: final int columnSpan = rectangle.getColumnSpan();
317: if (rowSpan > 1 || columnSpan > 1) {
318: sheet
319: .addMergedRegion(new Region(row, col, (row
320: + rowSpan - 1),
321: (short) (col + columnSpan - 1)));
322: final int rectX = rectangle.getX1();
323: final int rectY = rectangle.getY1();
324:
325: for (int spannedRow = 0; spannedRow < rowSpan; spannedRow += 1) {
326: for (int spannedCol = 0; spannedCol < columnSpan; spannedCol += 1) {
327: final TableCellDefinition bg = sheetLayout
328: .getBackgroundAt(rectY + spannedRow, rectX
329: + spannedCol);
330: final HSSFCell regionCell = getCellAt(
331: (short) (col + spannedCol), row
332: + spannedRow);
333: final HSSFCellStyle spannedStyle = cellStyleProducer
334: .createCellStyle(content, bg);
335: if (spannedStyle != null) {
336: regionCell.setCellStyle(spannedStyle);
337: }
338: }
339: }
340: }
341: }
342:
343: /**
344: * Applies the cell value and determines whether the cell should be merged. Merging will only take place if the cell
345: * has a row or colspan greater than one. Images will never be merged, as image content is rendered into an anchored
346: * frame on top of the cells.
347: *
348: * @param content
349: * @param cell
350: * @param sheetLayout
351: * @param rectangle
352: * @return true, if the cell may to be put into a merged region, false otherwise.
353: */
354: private boolean applyCellValue(
355: final OutputProcessorMetaData metaData,
356: final RenderBox content, final HSSFCell cell,
357: final SheetLayout sheetLayout,
358: final TableRectangle rectangle, final long contentOffset) {
359: final ExcelTextExtractor etx = new ExcelTextExtractor(metaData);
360: final Object value = etx.compute(content, cellStyleProducer
361: .getFontFactory());
362:
363: if (value instanceof Image) {
364: try {
365: final ImageContainer imageContainer = new DefaultImageReference(
366: (Image) value);
367: final RenderNode rawSource = etx.getRawSource();
368: final StrictBounds contentBounds = new StrictBounds(
369: content.getX(), content.getY() + contentOffset,
370: content.getWidth(), content.getHeight());
371: createImageCell(rawSource, imageContainer, sheetLayout,
372: rectangle, contentBounds);
373: } catch (IOException ioe) {
374: // Should not happen.
375: Log.warn("Failed to process AWT-Image in Excel-Export",
376: ioe);
377: }
378: return false;
379: } else if (value instanceof ImageContainer) {
380: final ImageContainer imageContainer = (ImageContainer) value;
381: final RenderNode rawSource = etx.getRawSource();
382: final StrictBounds contentBounds = new StrictBounds(content
383: .getX(), content.getY() + contentOffset, content
384: .getWidth(), content.getHeight());
385: createImageCell(rawSource, imageContainer, sheetLayout,
386: rectangle, contentBounds);
387: return false;
388: } else if (value instanceof Drawable) {
389: final Drawable drawable = (Drawable) value;
390: final RenderNode rawSource = etx.getRawSource();
391: final StrictBounds contentBounds = new StrictBounds(
392: rawSource.getX(), rawSource.getY() + contentOffset,
393: rawSource.getWidth(), rawSource.getHeight());
394: final ImageContainer imageFromDrawable = RenderUtility
395: .createImageFromDrawable(drawable, contentBounds,
396: content, metaData);
397: createImageCell(rawSource, imageFromDrawable, sheetLayout,
398: rectangle, contentBounds);
399: return false;
400: } else if (value instanceof Shape) {
401: // We *could* do this as well ... but for now we dont.
402: return false;
403: }
404:
405: final String linkTarget = (String) content.getStyleSheet()
406: .getStyleProperty(ElementStyleKeys.HREF_TARGET);
407: if (linkTarget != null) {
408: // this may be wrong if we have quotes inside. We should escape them ..
409: cell.setCellFormula("HYPERLINK(\"" + linkTarget + "\",\""
410: + etx.getText() + "\")");
411: } else if (value instanceof HSSFRichTextString) {
412: cell.setCellValue((HSSFRichTextString) value);
413: } else if (value instanceof Date) {
414: cell.setCellValue((Date) value);
415: } else if (value instanceof Number) {
416: final Number number = (Number) value;
417: cell.setCellValue(number.doubleValue());
418: } else if (value instanceof Boolean) {
419: cell.setCellValue(Boolean.TRUE.equals(value));
420: } else if (value instanceof Anchor) {
421: // Anchors are not printable and therefore ignored.
422: } else // Something we can't handle.
423: {
424: if (value == null) {
425: cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
426: } else {
427: cell.setCellValue(new HSSFRichTextString(String
428: .valueOf(value)));
429: }
430: }
431: return true;
432: }
433:
434: private void configureSheet(final PhysicalPageBox page) {
435: // make sure a new patriarch is created if needed.
436: patriarch = null;
437:
438: final String paper = config
439: .getConfigProperty(ExcelTableModule.CONFIGURATION_PREFIX
440: + ".Paper");
441: final String orientation = config
442: .getConfigProperty(ExcelTableModule.CONFIGURATION_PREFIX
443: + ".PaperOrientation");
444:
445: final HSSFPrintSetup printSetup = sheet.getPrintSetup();
446: ExcelPrintSetupFactory.performPageSetup(printSetup, page,
447: paper, orientation);
448:
449: final boolean displayGridLines = "true"
450: .equals(config
451: .getConfigProperty(ExcelTableModule.CONFIGURATION_PREFIX
452: + ".GridLinesDisplayed"));
453: final boolean printGridLines = "true"
454: .equals(config
455: .getConfigProperty(ExcelTableModule.CONFIGURATION_PREFIX
456: + ".GridLinesPrinted"));
457: sheet.setDisplayGridlines(displayGridLines);
458: sheet.setPrintGridlines(printGridLines);
459: }
460:
461: public void close() {
462: if (workbook != null) {
463: try {
464: workbook.write(outputStream);
465: // cleanup..
466: patriarch = null;
467: sheet = null;
468: outputStream.flush();
469: } catch (IOException e) {
470: Log.warn("could not write xls data. Message:", e);
471: } finally {
472: workbook = null;
473: }
474: }
475:
476: }
477:
478: private HSSFWorkbook createWorkbook() {
479: // Not opened yet. Lets do this now.
480: if (templateInputStream != null) {
481: // do some preprocessing ..
482: try {
483: final POIFSFileSystem fs = new POIFSFileSystem(
484: templateInputStream);
485: final HSSFWorkbook workbook = new HSSFWorkbook(fs);
486:
487: // OK, we have a workbook, but we can't stop here..
488: final int sheetCount = workbook.getNumberOfSheets();
489: for (int i = 0; i < sheetCount; i++) {
490: final String sheetName = workbook.getSheetName(i);
491: // make sure that that name is marked as used ..
492: makeUnique(sheetName);
493: }
494:
495: // todo: Read in the existing styles, maybe we can reuse some of them ..
496: return workbook;
497: } catch (IOException e) {
498: Log.warn("Unable to read predefined xls-data.", e);
499: }
500: }
501: return new HSSFWorkbook();
502: }
503:
504: private HSSFSheet openSheet(final String sheetName) {
505: if (sheetName == null) {
506: return workbook.createSheet();
507: } else {
508: final String uniqueSheetname = makeUnique(sheetName);
509: if (uniqueSheetname.length() == 0
510: || uniqueSheetname.length() > 31) {
511: Log
512: .warn("A sheet name must not be empty and greater than 31 characters");
513: return workbook.createSheet();
514: } else if (isValidSheetName(uniqueSheetname) == false) {
515: Log
516: .warn("A sheet name must not contain any of ':/\\*?[]'");
517: // OpenOffice is even more restrictive and only allows Letters,
518: // Digits, Spaces and the Underscore
519: return workbook.createSheet();
520: } else {
521: return workbook.createSheet(uniqueSheetname);
522: }
523: }
524: }
525:
526: /**
527: * Produces the content for image or drawable cells. Excel does not support image-content in cells. Images are
528: * rendered to an embedded OLE canvas instead, which is then positioned over the cell that would contain the image.
529: *
530: * @param contentNode the render node that contains the image.
531: * @param image the image object
532: * @param currentLayout the current sheet layout containing all row and column breaks
533: * @param rectangle the current cell in grid-coordinates
534: * @param cellBounds the bounds of the cell.
535: */
536: private void createImageCell(final RenderNode contentNode,
537: final ImageContainer image,
538: final SheetLayout currentLayout, TableRectangle rectangle,
539: final StrictBounds cellBounds) {
540: try {
541: if (rectangle == null) {
542: // there was an error while computing the grid-position for this
543: // element. Evil me...
544: Log
545: .debug("Invalid reference: I was not able to compute "
546: + "the rectangle for the content.");
547: return;
548: }
549:
550: final StyleSheet layoutContext = contentNode
551: .getStyleSheet();
552: final boolean shouldScale = layoutContext
553: .getBooleanStyleProperty(ElementStyleKeys.SCALE);
554:
555: final int imageWidth = image.getImageWidth();
556: final int imageHeight = image.getImageHeight();
557: if (imageWidth < 1 || imageHeight < 1) {
558: return;
559: }
560:
561: final double scaleFactor;
562: final double devResolution = metaData
563: .getNumericFeatureValue(OutputProcessorFeature.DEVICE_RESOLUTION);
564: if (metaData
565: .isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING)) {
566: if (devResolution != 72.0 && devResolution > 0) {
567: // Need to scale the device to its native resolution before attempting to draw the image..
568: scaleFactor = (devResolution / 72.0);
569:
570: } else {
571: scaleFactor = 1;
572: }
573: } else {
574: scaleFactor = 1;
575: }
576:
577: final ElementAlignment horizontalAlignment = (ElementAlignment) layoutContext
578: .getStyleProperty(ElementStyleKeys.ALIGNMENT);
579: final ElementAlignment verticalAlignment = (ElementAlignment) layoutContext
580: .getStyleProperty(ElementStyleKeys.VALIGNMENT);
581:
582: final long internalImageWidth = StrictGeomUtility
583: .toInternalValue(scaleFactor * imageWidth);
584: final long internalImageHeight = StrictGeomUtility
585: .toInternalValue(scaleFactor * imageHeight);
586:
587: final long cellWidth = cellBounds.getWidth();
588: final long cellHeight = cellBounds.getHeight();
589:
590: final StrictBounds cb;
591: final int pictureId;
592: if (shouldScale) {
593: final double scaleX;
594: final double scaleY;
595:
596: final boolean keepAspectRatio = layoutContext
597: .getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO);
598: if (keepAspectRatio) {
599: final double imgScaleFactor = Math.min(cellWidth
600: / (double) internalImageWidth, cellHeight
601: / (double) internalImageHeight);
602: scaleX = imgScaleFactor;
603: scaleY = imgScaleFactor;
604: } else {
605: scaleX = cellWidth / (double) internalImageWidth;
606: scaleY = cellHeight / (double) internalImageHeight;
607: }
608:
609: final long clipWidth = (long) (scaleX * internalImageWidth);
610: final long clipHeight = (long) (scaleY * internalImageHeight);
611:
612: final long alignmentX = RenderUtility
613: .computeHorizontalAlignment(
614: horizontalAlignment, cellWidth,
615: clipWidth);
616: final long alignmentY = RenderUtility
617: .computeVerticalAlignment(verticalAlignment,
618: cellHeight, clipHeight);
619:
620: cb = new StrictBounds(cellBounds.getX() + alignmentX,
621: cellBounds.getY() + alignmentY, Math.min(
622: clipWidth, cellWidth), Math.min(
623: clipHeight, cellHeight));
624:
625: // Recompute the cells that this image will cover (now that it has been resized)
626: rectangle = currentLayout.getTableBounds(cb, rectangle);
627:
628: pictureId = loadImage(workbook, image);
629: if (pictureId <= 0) {
630: return;
631: }
632: } else {
633: // unscaled ..
634: if (internalImageWidth <= cellWidth
635: && internalImageHeight <= cellHeight) {
636: // No clipping needed.
637: final long alignmentX = RenderUtility
638: .computeHorizontalAlignment(
639: horizontalAlignment, cellBounds
640: .getWidth(),
641: internalImageWidth);
642: final long alignmentY = RenderUtility
643: .computeVerticalAlignment(
644: verticalAlignment, cellBounds
645: .getHeight(),
646: internalImageHeight);
647:
648: cb = new StrictBounds(cellBounds.getX()
649: + alignmentX, cellBounds.getY()
650: + alignmentY, internalImageWidth,
651: internalImageHeight);
652:
653: // Recompute the cells that this image will cover (now that it has been resized)
654: rectangle = currentLayout.getTableBounds(cb,
655: rectangle);
656:
657: pictureId = loadImage(workbook, image);
658: if (pictureId <= 0) {
659: return;
660: }
661: } else {
662: // at least somewhere there is clipping needed.
663: final long clipWidth = Math.min(cellWidth,
664: internalImageWidth);
665: final long clipHeight = Math.min(cellHeight,
666: internalImageHeight);
667: final long alignmentX = RenderUtility
668: .computeHorizontalAlignment(
669: horizontalAlignment, cellBounds
670: .getWidth(), clipWidth);
671: final long alignmentY = RenderUtility
672: .computeVerticalAlignment(
673: verticalAlignment, cellBounds
674: .getHeight(), clipHeight);
675: cb = new StrictBounds(cellBounds.getX()
676: + alignmentX, cellBounds.getY()
677: + alignmentY, clipWidth, clipHeight);
678:
679: // Recompute the cells that this image will cover (now that it has been resized)
680: rectangle = currentLayout.getTableBounds(cb,
681: rectangle);
682:
683: pictureId = loadImageWithClipping(workbook, image,
684: clipWidth, clipHeight, scaleFactor);
685: if (pictureId <= 0) {
686: return;
687: }
688: }
689: }
690:
691: final int cell1x = rectangle.getX1();
692: final int cell1y = rectangle.getY1();
693: final int cell2x = Math.max(cell1x, rectangle.getX2() - 1);
694: final int cell2y = Math.max(cell1y, rectangle.getY2() - 1);
695:
696: final long cell1width = currentLayout.getCellWidth(cell1x);
697: final long cell1height = currentLayout.getRowHeight(cell1y);
698: final long cell2width = currentLayout.getCellWidth(cell2x);
699: final long cell2height = currentLayout.getRowHeight(cell2y);
700:
701: final long cell1xPos = currentLayout.getXPosition(cell1x);
702: final long cell1yPos = currentLayout.getYPosition(cell1y);
703: final long cell2xPos = currentLayout.getXPosition(cell2x);
704: final long cell2yPos = currentLayout.getYPosition(cell2y);
705:
706: final int dx1 = (int) (1023 * ((cb.getX() - cell1xPos) / (double) cell1width));
707: final int dy1 = (int) (255 * ((cb.getY() - cell1yPos) / (double) cell1height));
708: final int dx2 = (int) (1023 * ((cb.getX() + cb.getWidth() - cell2xPos) / (double) cell2width));
709: final int dy2 = (int) (255 * ((cb.getY() + cb.getHeight() - cell2yPos) / (double) cell2height));
710:
711: final HSSFClientAnchor anchor = new HSSFClientAnchor(dx1,
712: dy1, dx2, dy2, (short) cell1x, cell1y,
713: (short) cell2x, cell2y);
714: anchor.setAnchorType(2); // Move, but don't size
715: if (patriarch == null) {
716: patriarch = sheet.createDrawingPatriarch();
717: }
718: patriarch.createPicture(anchor, pictureId);
719: } catch (IOException e) {
720: Log.warn("Failed to add image. Ignoring.");
721: }
722: }
723:
724: private int getImageFormat(final URL sourceURL) {
725: final String file = sourceURL.getFile();
726: if (StringUtils.endsWithIgnoreCase(file, ".png")) {
727: return HSSFWorkbook.PICTURE_TYPE_PNG;
728: }
729: if (StringUtils.endsWithIgnoreCase(file, ".jpg")
730: || StringUtils.endsWithIgnoreCase(file, ".jpeg")) {
731: return HSSFWorkbook.PICTURE_TYPE_JPEG;
732: }
733: if (StringUtils.endsWithIgnoreCase(file, ".bmp")
734: || StringUtils.endsWithIgnoreCase(file, ".ico")) {
735: return HSSFWorkbook.PICTURE_TYPE_DIB;
736: }
737: return -1;
738: }
739:
740: private int loadImageWithClipping(final HSSFWorkbook workbook,
741: final ImageContainer reference, final long clipWidth,
742: final long clipHeight, final double deviceScaleFactor)
743: throws IOException {
744:
745: Image image = null;
746: // The image has an assigned URL ...
747: if (reference instanceof URLImageContainer) {
748: final URLImageContainer urlImage = (URLImageContainer) reference;
749: final URL url = urlImage.getSourceURL();
750: // if we have an source to load the image data from ..
751: if (url != null && urlImage.isLoadable()) {
752: if (reference instanceof LocalImageContainer) {
753: final LocalImageContainer li = (LocalImageContainer) reference;
754: image = li.getImage();
755: }
756: if (image == null) {
757: image = ImageFactory.getInstance().createImage(url);
758: }
759: }
760: }
761:
762: if (reference instanceof LocalImageContainer) {
763: // Check, whether the imagereference contains an AWT image.
764: // if so, then we can use that image instance for the recoding
765: final LocalImageContainer li = (LocalImageContainer) reference;
766: if (image == null) {
767: image = li.getImage();
768: }
769: }
770:
771: if (image != null) {
772: // now encode the image. We don't need to create digest data
773: // for the image contents, as the image is perfectly identifyable
774: // by its URL
775: return clipAndEncodeImage(workbook, image, clipWidth,
776: clipHeight, deviceScaleFactor);
777: }
778: return -1;
779: }
780:
781: private int clipAndEncodeImage(final HSSFWorkbook workbook,
782: final Image image, final long width, final long height,
783: final double deviceScaleFactor) {
784: final int imageWidth = (int) StrictGeomUtility
785: .toExternalValue(width);
786: final int imageHeight = (int) StrictGeomUtility
787: .toExternalValue(height);
788: // first clip.
789: final BufferedImage bi = ImageUtils.createTransparentImage(
790: imageWidth, imageHeight);
791: final Graphics2D graphics = (Graphics2D) bi.getGraphics();
792: graphics.scale(deviceScaleFactor, deviceScaleFactor);
793:
794: if (image instanceof BufferedImage) {
795: // final int imgW = image.getWidth(null);
796: // final int imgH = image.getHeight(null);
797: // graphics.translate((imageWidth - imgW) >> 1, (imageHeight - imgH) >> 1);
798: if (graphics.drawImage(image, null, null) == false) {
799: Log
800: .debug("Failed to render the image. This should not happen for BufferedImages");
801: }
802: } else {
803: final WaitingImageObserver obs = new WaitingImageObserver(
804: image);
805: obs.waitImageLoaded();
806: // final int imgW = image.getWidth(obs);
807: // final int imgH = image.getHeight(obs);
808: // graphics.translate((imageWidth - imgW) >> 1, (imageHeight - imgH) >> 1);
809:
810: while (graphics.drawImage(image, null, obs) == false) {
811: obs.waitImageLoaded();
812: if (obs.isError()) {
813: Log
814: .warn("Error while loading the image during the rendering.");
815: break;
816: }
817: }
818: }
819:
820: graphics.dispose();
821: final byte[] data = RenderUtility.encodeImage(bi);
822: return workbook.addPicture(data, HSSFWorkbook.PICTURE_TYPE_PNG);
823:
824: }
825:
826: private int loadImage(final HSSFWorkbook workbook,
827: final ImageContainer reference) throws IOException {
828: Image image = null;
829: // The image has an assigned URL ...
830: if (reference instanceof URLImageContainer) {
831: final URLImageContainer urlImage = (URLImageContainer) reference;
832: final URL url = urlImage.getSourceURL();
833: // if we have an source to load the image data from ..
834: if (url != null && urlImage.isLoadable()) {
835: // and the image is one of the supported image formats ...
836: // we we can embedd it directly ...
837: final int format = getImageFormat(urlImage
838: .getSourceURL());
839: if (format == -1) {
840: // This is a unsupported image format.
841: if (reference instanceof LocalImageContainer) {
842: final LocalImageContainer li = (LocalImageContainer) reference;
843: image = li.getImage();
844: }
845: if (image == null) {
846: image = ImageFactory.getInstance().createImage(
847: url);
848: }
849: } else {
850: final MemoryByteArrayOutputStream bout = new MemoryByteArrayOutputStream();
851: final InputStream urlIn = new BufferedInputStream(
852: urlImage.getSourceURL().openStream());
853: try {
854: IOUtils.getInstance().copyStreams(urlIn, bout);
855: } finally {
856: bout.close();
857: urlIn.close();
858: }
859:
860: final byte[] data = bout.toByteArray();
861: // create the image
862: return workbook.addPicture(data, format);
863: }
864: }
865: }
866:
867: if (reference instanceof LocalImageContainer) {
868: // Check, whether the imagereference contains an AWT image.
869: // if so, then we can use that image instance for the recoding
870: final LocalImageContainer li = (LocalImageContainer) reference;
871: if (image == null) {
872: image = li.getImage();
873: }
874: }
875:
876: if (image != null) {
877: // now encode the image. We don't need to create digest data
878: // for the image contents, as the image is perfectly identifyable
879: // by its URL
880: final byte[] data = RenderUtility.encodeImage(image);
881: return workbook.addPicture(data,
882: HSSFWorkbook.PICTURE_TYPE_PNG);
883: }
884: return -1;
885: }
886:
887: }
|