Package org.pentaho.reporting.engine.classic.core.modules.output.table.xls.helper

Source Code of org.pentaho.reporting.engine.classic.core.modules.output.table.xls.helper.ExcelPrinter

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.engine.classic.core.modules.output.table.xls.helper;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Picture;
import org.apache.poi.ss.usermodel.PrintSetup;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.pentaho.reporting.engine.classic.core.Anchor;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.DefaultImageReference;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.ImageContainer;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.LocalImageContainer;
import org.pentaho.reporting.engine.classic.core.URLImageContainer;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.PhysicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.output.LogicalPageKey;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.AbstractTableOutputProcessor;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellBackground;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellBackgroundProducer;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.SheetLayout;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.TableContentProducer;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.TableRectangle;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.ImageUtils;
import org.pentaho.reporting.engine.classic.core.util.IntegerCache;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.encoder.UnsupportedEncoderException;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.base.util.WaitingImageObserver;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceData;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
import org.pentaho.reporting.libraries.xmlns.common.ParserUtil;

/**
* Creation-Date: 09.05.2007, 14:52:05
*
* @author Thomas Morgner
*/
public class ExcelPrinter
{
  private static final Log logger = LogFactory.getLog(ExcelPrinter.class);

  private InputStream templateInputStream;
  private OutputStream outputStream;
  private Workbook workbook;
  private HashMap sheetNamesCount;
  private double scaleFactor;
  private Configuration config;
  private OutputProcessorMetaData metaData;
  private ResourceManager resourceManager;
  private Sheet sheet;
  private Drawing patriarch;
  private HSSFCellStyleProducer cellStyleProducer;
  private CellBackgroundProducer cellBackgroundProducer;
  private ExcelTextExtractor textExtractor;
  private ExcelColorProducer colorProducer;
  private ExcelColorProducer fontColorProducer;
  private boolean useXlsxFormat;

  public ExcelPrinter()
  {
    this.sheetNamesCount = new HashMap();
  }

  public boolean isUseXlsxFormat()
  {
    return useXlsxFormat;
  }

  public void setUseXlsxFormat(final boolean useXlsxFormat)
  {
    this.useXlsxFormat = useXlsxFormat;
  }

  public void init(final Configuration config,
                   final OutputProcessorMetaData metaData,
                   final OutputStream outputStream,
                   final ResourceManager resourceManager)
  {
    if (config == null)
    {
      throw new NullPointerException();
    }
    if (metaData == null)
    {
      throw new NullPointerException();
    }
    if (outputStream == null)
    {
      throw new NullPointerException();
    }
    if (resourceManager == null)
    {
      throw new NullPointerException();
    }

    this.outputStream = outputStream;
    this.config = config;
    this.metaData = metaData;
    this.resourceManager = resourceManager;
    this.cellBackgroundProducer = new CellBackgroundProducer
        (metaData.isFeatureSupported(AbstractTableOutputProcessor.TREAT_ELLIPSE_AS_RECTANGLE),
            metaData.isFeatureSupported(OutputProcessorFeature.UNALIGNED_PAGEBANDS));

    try
    {
      final String scaleFactorText = config.getConfigProperty
          ("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.CellWidthScaleFactor");
      if (scaleFactorText == null)
      {
        scaleFactor = 50;
      }
      else
      {
        scaleFactor = Double.parseDouble(scaleFactorText);
      }
    }
    catch (Exception e)
    {
      this.scaleFactor = 50;
    }
  }

  public InputStream getTemplateInputStream()
  {
    return templateInputStream;
  }

  public void setTemplateInputStream(final InputStream templateInputStream)
  {
    this.templateInputStream = templateInputStream;
  }

  private String makeUnique(final String name)
  {
    if (name == null)
    {
      throw new NullPointerException();
    }

    final Integer count = (Integer) sheetNamesCount.get(name);
    if (count == null)
    {
      sheetNamesCount.put(name, IntegerCache.getInteger(1));
      return name;
    }

    final int value = count.intValue() + 1;
    sheetNamesCount.put(name, IntegerCache.getInteger(value));
    return makeUnique(name + ' ' + value);
  }

  private boolean isValidSheetName(final String sheetname)
  {
    if ((sheetname.indexOf('/') > -1)
        || (sheetname.indexOf('\\') > -1)
        || (sheetname.indexOf('?') > -1)
        || (sheetname.indexOf('*') > -1)
        || (sheetname.indexOf(']') > -1)
        || (sheetname.indexOf('[') > -1)
        || (sheetname.indexOf(':') > -1))
    {
      return false;
    }

    return true;
  }

  private Cell getCellAt(final short x, final int y)
  {
    final Row row = getRowAt(y);
    final Cell cell = row.getCell(x);
    if (cell != null)
    {
      return cell;
    }
    return row.createCell(x);
  }

  private Row getRowAt(final int y)
  {
    final Row row = sheet.getRow(y);
    if (row != null)
    {
      return row;
    }
    return sheet.createRow(y);
  }

  private boolean isHeaderFooterValid(final String left, final String center, final String right)
  {
    int length = 0;
    if (left != null)
    {
      length += left.length();
    }
    if (center != null)
    {
      length += center.length();
    }
    if (right != null)
    {
      length += right.length();
    }
    return length < 255;
  }

  public void print(final LogicalPageKey logicalPageKey,
                    final LogicalPageBox logicalPage,
                    final TableContentProducer contentProducer,
                    final boolean incremental)
  {
    if (workbook == null)
    {
      workbook = createWorkbook();

      if (workbook instanceof HSSFWorkbook)
      {
        final boolean dynamicColors = "true".equals
            (config.getConfigProperty(
                "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.DynamicColors"));
        if (dynamicColors)
        {
          final HSSFWorkbook hssfWorkbook = (HSSFWorkbook) workbook;
          colorProducer = new DynamicExcelColorProducer(hssfWorkbook);
        }
        else
        {
          colorProducer = new StaticExcelColorSupport();
        }
        fontColorProducer = colorProducer;
      }
      else
      {
        colorProducer = new XSSFExcelColorProducer();
        fontColorProducer = new StaticExcelColorSupport();
      }

      this.textExtractor = new ExcelTextExtractor(metaData, colorProducer);

      final boolean hardLimit = "true".equals
          (config.getConfigProperty(
              "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.HardStyleCountLimit"));
      cellStyleProducer = new HSSFCellStyleProducer(workbook, hardLimit, colorProducer, fontColorProducer);
    }

    if (sheet == null)
    {
      sheet = openSheet(contentProducer.getSheetName());
      final ExcelTableContentProducer excelTableContentProducer = (ExcelTableContentProducer) contentProducer;
      final String pageHeaderCenter = excelTableContentProducer.getPageHeaderCenter();
      final String pageFooterCenter = excelTableContentProducer.getPageFooterCenter();
      final String pageHeaderLeft = excelTableContentProducer.getPageHeaderLeft();
      final String pageFooterLeft = excelTableContentProducer.getPageFooterLeft();
      final String pageHeaderRight = excelTableContentProducer.getPageHeaderRight();
      final String pageFooterRight = excelTableContentProducer.getPageFooterRight();

      if (isHeaderFooterValid(pageHeaderLeft, pageHeaderCenter, pageHeaderRight))
      {
        if (pageHeaderLeft != null)
        {
          sheet.getHeader().setLeft(pageHeaderLeft);
        }
        if (pageHeaderCenter != null)
        {
          sheet.getHeader().setCenter(pageHeaderCenter);
        }
        if (pageHeaderRight != null)
        {
          sheet.getHeader().setRight(pageHeaderRight);
        }
      }
      else
      {
        ExcelPrinter.logger.warn(
            "Page-Header exceeds the maximum length of 255 characters. No page-header will be added to the sheet.");
      }
      if (isHeaderFooterValid(pageFooterLeft, pageFooterCenter, pageFooterRight))
      {
        if (pageFooterCenter != null)
        {
          sheet.getFooter().setCenter(pageFooterCenter);
        }
        if (pageFooterLeft != null)
        {
          sheet.getFooter().setLeft(pageFooterLeft);
        }
        if (pageFooterRight != null)
        {
          sheet.getFooter().setRight(pageFooterRight);
        }
      }
      else
      {
        ExcelPrinter.logger.warn(
            "Page-Footer exceeds the maximum length of 255 characters. No page-footer will be added to the sheet.");
      }

      // Start a new page.
      final PhysicalPageBox page = logicalPage.getPageGrid().getPage(0, 0);
      configureSheet(page);

      // Set column widths ..
      final SheetLayout sheetLayout = contentProducer.getSheetLayout();
      final int columnCount = contentProducer.getColumnCount();
      for (short col = 0; col < columnCount; col++)
      {
        final double cellWidth = StrictGeomUtility.toExternalValue(sheetLayout.getCellWidth(col, col + 1));
        final double poiCellWidth = (cellWidth * scaleFactor);
        sheet.setColumnWidth(col, (short) poiCellWidth);
      }
    }

    // and finally the content ..
    final SheetLayout sheetLayout = contentProducer.getSheetLayout();
    final int colCount = sheetLayout.getColumnCount();
    final int startRow = contentProducer.getFinishedRows();
    final int finishRow = contentProducer.getFilledRows();

    for (int row = startRow; row < finishRow; row++)
    {
      final Row hssfRow = getRowAt(row);
      final double lastRowHeight = StrictGeomUtility.toExternalValue(sheetLayout.getRowHeight(row));
      hssfRow.setHeightInPoints((float) (lastRowHeight));

      for (short col = 0; col < colCount; col++)
      {
        final RenderBox content = contentProducer.getContent(row, col);
        if (content == null)
        {
          final int sectionType = contentProducer.getSectionType(row, col);
          final CellBackground background =
              cellBackgroundProducer.getBackgroundAt(logicalPage, sheetLayout, col, row, 1, 1, false, sectionType);
          if (background == null)
          {
            if (row == 0 && col == 0)
            {
              // create a single cell, so that we dont run into nullpointer inside POI..
              getCellAt(col, row);
            }
            // An empty cell .. ignore
            continue;
          }

          // A empty cell with a defined background ..
          final Cell cell = getCellAt(col, row);
          final CellStyle style = cellStyleProducer.createCellStyle(null, background);
          if (style != null)
          {
            cell.setCellStyle(style);
          }
          continue;
        }

        if (content.isCommited() == false)
        {
          throw new InvalidReportStateException("Uncommited content encountered");
        }

        final long contentOffset = contentProducer.getContentOffset(row, col);
        final TableRectangle rectangle = sheetLayout.getTableBounds
            (content.getX(), content.getY() + contentOffset,
                content.getWidth(), content.getHeight(), null);
        if (rectangle.isOrigin(col, row) == false)
        {
          // A spanned cell ..
          continue;
        }

        final int sectionType = contentProducer.getSectionType(row, col);
        final CellBackground realBackground = cellBackgroundProducer.getBackgroundAt
            (logicalPage, sheetLayout,
                rectangle.getX1(), rectangle.getY1(), rectangle.getColumnSpan(), rectangle.getRowSpan(), false, sectionType);

        // export the cell and all content ..

        final Cell cell = getCellAt(col, row);
        final CellStyle style = cellStyleProducer.createCellStyle(content, realBackground);
        if (style != null)
        {
          cell.setCellStyle(style);
        }

        if (applyCellValue(metaData, content, cell, sheetLayout, rectangle, contentOffset))
        {
          mergeCellRegion(rectangle, row, col, sheetLayout, logicalPage, content, contentProducer);
        }

        content.setFinished(true);
      }

    }

    if (incremental == false)
    {
      // cleanup ..
      patriarch = null;
      sheet = null;
    }
  }

  private void mergeCellRegion(final TableRectangle rectangle,
                               final int row,
                               final short col,
                               final SheetLayout sheetLayout,
                               final LogicalPageBox logicalPage,
                               final RenderBox content,
                               final TableContentProducer contentProducer)
  {
    final int rowSpan = rectangle.getRowSpan();
    final int columnSpan = rectangle.getColumnSpan();
    if (rowSpan > 1 || columnSpan > 1)
    {
      sheet.addMergedRegion(new CellRangeAddress(row, (row + rowSpan - 1), col, (short) (col + columnSpan - 1)));
      final int rectX = rectangle.getX1();
      final int rectY = rectangle.getY1();

      for (int spannedRow = 0; spannedRow < rowSpan; spannedRow += 1)
      {
        for (int spannedCol = 0; spannedCol < columnSpan; spannedCol += 1)
        {
          final int sectionType = contentProducer.getSectionType(row, col);
          final CellBackground bg = cellBackgroundProducer.getBackgroundAt
              (logicalPage, sheetLayout, rectX + spannedCol, rectY + spannedRow, 1, 1, false, sectionType);
          final Cell regionCell = getCellAt((short) (col + spannedCol), row + spannedRow);
          final CellStyle spannedStyle = cellStyleProducer.createCellStyle(content, bg);
          if (spannedStyle != null)
          {
            regionCell.setCellStyle(spannedStyle);
          }
        }
      }
    }
  }

  /**
   * Applies the cell value and determines whether the cell should be merged. Merging will only take place if the cell
   * has a row or colspan greater than one. Images will never be merged, as image content is rendered into an anchored
   * frame on top of the cells.
   *
   * @param content
   * @param cell
   * @param sheetLayout
   * @param rectangle
   * @return true, if the cell may to be put into a merged region, false otherwise.
   */
  private boolean applyCellValue(final OutputProcessorMetaData metaData,
                                 final RenderBox content,
                                 final Cell cell,
                                 final SheetLayout sheetLayout,
                                 final TableRectangle rectangle,
                                 final long contentOffset)
  {
    final Object value = textExtractor.compute(content, cellStyleProducer.getFontFactory(), workbook.getCreationHelper());

    if (value instanceof Image)
    {
      try
      {
        final ImageContainer imageContainer = new DefaultImageReference((Image) value);
        final RenderNode rawSource = textExtractor.getRawSource();
        final StrictBounds contentBounds =
            new StrictBounds(content.getX(), content.getY() + contentOffset, content.getWidth(), content.getHeight());
        createImageCell(rawSource, imageContainer, sheetLayout, rectangle, contentBounds);
      }
      catch (IOException ioe)
      {
        // Should not happen.
        ExcelPrinter.logger.warn("Failed to process AWT-Image in Excel-Export", ioe);
      }
      return false;
    }
    else if (value instanceof ImageContainer)
    {
      final ImageContainer imageContainer = (ImageContainer) value;
      // todo: this is wrong ..
      final RenderNode rawSource = textExtractor.getRawSource();
      final StrictBounds contentBounds =
          new StrictBounds(content.getX(), content.getY() + contentOffset, content.getWidth(), content.getHeight());
      createImageCell(rawSource, imageContainer, sheetLayout, rectangle, contentBounds);
      return false;
    }
    else if (value instanceof DrawableWrapper)
    {
      final DrawableWrapper drawable = (DrawableWrapper) value;
      final RenderNode rawSource = textExtractor.getRawSource();
      final StrictBounds contentBounds = new StrictBounds
          (rawSource.getX(), rawSource.getY() + contentOffset, rawSource.getWidth(), rawSource.getHeight());
      final ImageContainer imageFromDrawable =
          RenderUtility.createImageFromDrawable(drawable, contentBounds, content, metaData);
      createImageCell(rawSource, imageFromDrawable, sheetLayout, rectangle, contentBounds);
      return false;
    }
    else if (value instanceof Shape)
    {
      // We *could* do this as well ... but for now we dont.
      return false;
    }


    final String linkTarget = (String) content.getStyleSheet().getStyleProperty(ElementStyleKeys.HREF_TARGET);
    if (linkTarget != null)
    {
      // this may be wrong if we have quotes inside. We should escape them ..
      final String formula = "HYPERLINK(" + excelFormulaSplitAndQuote(linkTarget) +
          "," + excelFormulaSplitAndQuote(textExtractor.getText()) + ")";
      if (formula.length() < 1024)
      {
        cell.setCellFormula(formula);
        return true;
      }

      ExcelPrinter.logger.warn(
          "Excel-Cells cannot contain formulas longer than 1023 characters. Converting hyperlink into plain text");
    }

    final Object attr1 = content.getAttributes().getAttribute(AttributeNames.Excel.NAMESPACE,
        AttributeNames.Excel.FIELD_FORMULA);
    if (attr1 != null)
    {
      final String formula = String.valueOf(attr1);
      if (formula.length() < 1024)
      {
        cell.setCellFormula(formula);
        return true;
      }

      ExcelPrinter.logger.warn(
          "Excel-Cells cannot contain formulas longer than 1023 characters. Converting excel formula into plain text");
    }

    if (value instanceof RichTextString)
    {
      cell.setCellValue((RichTextString) value);
    }
    else if (value instanceof Date)
    {
      cell.setCellValue((Date) value);
    }
    else if (value instanceof Number)
    {
      final Number number = (Number) value;
      cell.setCellValue(number.doubleValue());
    }
    else if (value instanceof Boolean)
    {
      cell.setCellValue(Boolean.TRUE.equals(value));
    }
    else if (value instanceof Anchor)
    {
      // Anchors are not printable and therefore ignored.
    }
    else // Something we can't handle.
    {
      if (value == null)
      {
        cell.setCellType(Cell.CELL_TYPE_BLANK);
      }
      else
      {
        cell.setCellValue(String.valueOf(value));
      }
    }
    return true;
  }

  private String excelFormulaSplitAndQuote(String s)
  {
    final StringBuffer b = new StringBuffer();
    b.append('"');
    final char[] chars = s.toCharArray();
    int count = 0;
    for (int i = 0; i < chars.length; i++)
    {
      final char c = chars[i];
      if (c == '"')
      {
        b.append('"');
        b.append('"');
        count += 2;
      }
      else
      {
        b.append(c);
        count += 1;
      }
      if (count > 252)
      {
        count = 0;
        b.append("\" & \"");
      }
    }
    b.append('"');
    return b.toString();
  }

  private void configureSheet(final PhysicalPageBox page)
  {
    // make sure a new patriarch is created if needed.
    patriarch = null;

    final String paper = config.getConfigProperty(
        "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.Paper");
    final String orientation = config.getConfigProperty(
        "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PaperOrientation");
    final short scale = (short) ParserUtil.parseInt
        (config.getConfigProperty(
            "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintScaleFactor"), 100);
    final short hres = (short) ParserUtil.parseInt
        (config.getConfigProperty(
            "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintHorizontalResolution"), -1);
    final short vres = (short) ParserUtil.parseInt
        (config.getConfigProperty(
            "org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintVerticalResolution"), -1);
    final boolean noColors = "true".equals
        (config.getConfigProperty("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintNoColors"));
    final boolean notes = "true".equals
        (config.getConfigProperty("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintNotes"));
    final boolean usePage = "true".equals
        (config.getConfigProperty("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintUsePage"));
    final boolean draft = "true".equals
        (config.getConfigProperty("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.PrintDraft"));

    final PrintSetup printSetup = sheet.getPrintSetup();
    ExcelPrintSetupFactory.performPageSetup(printSetup, page, paper, orientation);
    printSetup.setScale(scale);
    printSetup.setNoColor(noColors);
    printSetup.setNotes(notes);
    printSetup.setUsePage(usePage);
    if (hres > 0)
    {
      printSetup.setHResolution(hres);
    }
    if (vres > 0)
    {
      printSetup.setVResolution(vres);
    }
    printSetup.setDraft(draft);

    final boolean displayGridLines = "true".equals(config.getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.GridLinesDisplayed"));
    final boolean printGridLines = "true".equals(config.getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.table.xls.GridLinesPrinted"));
    sheet.setDisplayGridlines(displayGridLines);
    sheet.setPrintGridlines(printGridLines);
  }

  public void close()
  {
    if (workbook != null)
    {
      try
      {
        workbook.write(outputStream);
        // cleanup..
        patriarch = null;
        sheet = null;
        outputStream.flush();
      }
      catch (IOException e)
      {
        ExcelPrinter.logger.warn("could not write xls data. Message:", e);
      }
      finally
      {
        workbook = null;
      }
    }

  }

  protected Workbook createWorkbook()
  {
    // Not opened yet. Lets do this now.
    if (templateInputStream != null)
    {
      // do some preprocessing ..
      try
      {
        final Workbook workbook = WorkbookFactory.create(templateInputStream);

        // OK, we have a workbook, but we can't stop here..
        final int sheetCount = workbook.getNumberOfSheets();
        for (int i = 0; i < sheetCount; i++)
        {
          final String sheetName = workbook.getSheetName(i);
          // make sure that that name is marked as used ..
          makeUnique(sheetName);
        }

        // todo: Read in the existing styles, maybe we can reuse some of them ..
        return workbook;
      }
      catch (IOException e)
      {
        ExcelPrinter.logger.warn("Unable to read predefined xls-data.", e);
      }
      catch (InvalidFormatException e)
      {
        ExcelPrinter.logger.warn("Unable to read predefined xls-data.", e);
      }
    }
    if (isUseXlsxFormat())
    {
      return new XSSFWorkbook();
    }
    else
    {
      return new HSSFWorkbook();
    }
  }

  private Sheet openSheet(final String sheetName)
  {
    if (sheetName == null)
    {
      return workbook.createSheet();
    }
    else
    {
      final String uniqueSheetname = makeUnique(sheetName);
      if (uniqueSheetname.length() == 0 || uniqueSheetname.length() > 31)
      {
        ExcelPrinter.logger.warn("A sheet name must not be empty and greater than 31 characters");
        return workbook.createSheet();
      }
      else if (isValidSheetName(uniqueSheetname) == false)
      {
        ExcelPrinter.logger.warn("A sheet name must not contain any of ':/\\*?[]'");
        // OpenOffice is even more restrictive and only allows Letters,
        // Digits, Spaces and the Underscore
        return workbook.createSheet();
      }
      else
      {
        return workbook.createSheet(uniqueSheetname);
      }
    }
  }


  /**
   * Produces the content for image or drawable cells. Excel does not support image-content in cells. Images are
   * rendered to an embedded OLE canvas instead, which is then positioned over the cell that would contain the image.
   *
   * @param contentNode   the render node that contains the image.
   * @param image         the image object
   * @param currentLayout the current sheet layout containing all row and column breaks
   * @param rectangle     the current cell in grid-coordinates
   * @param cellBounds    the bounds of the cell.
   */
  private void createImageCell(final RenderNode contentNode,
                               final ImageContainer image,
                               final SheetLayout currentLayout,
                               TableRectangle rectangle,
                               final StrictBounds cellBounds)
  {
    try
    {
      if (rectangle == null)
      {
        // there was an error while computing the grid-position for this
        // element. Evil me...
        ExcelPrinter.logger.debug("Invalid reference: I was not able to compute " +
            "the rectangle for the content.");
        return;
      }

      final StyleSheet layoutContext = contentNode.getStyleSheet();
      final boolean shouldScale = layoutContext.getBooleanStyleProperty(ElementStyleKeys.SCALE);

      final int imageWidth = image.getImageWidth();
      final int imageHeight = image.getImageHeight();
      if (imageWidth < 1 || imageHeight < 1)
      {
        return;
      }

      final double scaleFactor;
      final double devResolution = metaData.getNumericFeatureValue(OutputProcessorFeature.DEVICE_RESOLUTION);
      if (metaData.isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING))
      {
        if (devResolution != 72.0 && devResolution > 0)
        {
          // Need to scale the device to its native resolution before attempting to draw the image..
          scaleFactor = (72.0 / devResolution);

        }
        else
        {
          scaleFactor = 1;
        }
      }
      else
      {
        scaleFactor = 1;
      }

      final ElementAlignment horizontalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
      final ElementAlignment verticalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);

      final long internalImageWidth = StrictGeomUtility.toInternalValue(scaleFactor * imageWidth);
      final long internalImageHeight = StrictGeomUtility.toInternalValue(scaleFactor * imageHeight);

      final long cellWidth = cellBounds.getWidth();
      final long cellHeight = cellBounds.getHeight();

      final StrictBounds cb;
      final int pictureId;
      try
      {
        if (shouldScale)
        {
          final double scaleX;
          final double scaleY;

          final boolean keepAspectRatio = layoutContext.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO);
          if (keepAspectRatio)
          {
            final double imgScaleFactor = Math.min(cellWidth / (double) internalImageWidth,
                cellHeight / (double) internalImageHeight);
            scaleX = imgScaleFactor;
            scaleY = imgScaleFactor;
          }
          else
          {
            scaleX = cellWidth / (double) internalImageWidth;
            scaleY = cellHeight / (double) internalImageHeight;
          }

          final long clipWidth = (long) (scaleX * internalImageWidth);
          final long clipHeight = (long) (scaleY * internalImageHeight);

          final long alignmentX = RenderUtility.computeHorizontalAlignment(horizontalAlignment, cellWidth, clipWidth);
          final long alignmentY = RenderUtility.computeVerticalAlignment(verticalAlignment, cellHeight, clipHeight);

          cb = new StrictBounds(cellBounds.getX() + alignmentX,
              cellBounds.getY() + alignmentY,
              Math.min(clipWidth, cellWidth),
              Math.min(clipHeight, cellHeight));

          // Recompute the cells that this image will cover (now that it has been resized)
          rectangle = currentLayout.getTableBounds(cb, rectangle);

          pictureId = loadImage(workbook, image);
          if (pictureId <= 0)
          {
            return;
          }
        }
        else
        {
          // unscaled ..
          if (internalImageWidth <= cellWidth &&
              internalImageHeight <= cellHeight)
          {
            // No clipping needed.
            final long alignmentX = RenderUtility.computeHorizontalAlignment
                (horizontalAlignment, cellBounds.getWidth(), internalImageWidth);
            final long alignmentY = RenderUtility.computeVerticalAlignment
                (verticalAlignment, cellBounds.getHeight(), internalImageHeight);

            cb = new StrictBounds(cellBounds.getX() + alignmentX,
                cellBounds.getY() + alignmentY,
                internalImageWidth,
                internalImageHeight);

            // Recompute the cells that this image will cover (now that it has been resized)
            rectangle = currentLayout.getTableBounds(cb, rectangle);

            pictureId = loadImage(workbook, image);
            if (pictureId <= 0)
            {
              return;
            }
          }
          else
          {
            // at least somewhere there is clipping needed.
            final long clipWidth = Math.min(cellWidth, internalImageWidth);
            final long clipHeight = Math.min(cellHeight, internalImageHeight);
            final long alignmentX = RenderUtility.computeHorizontalAlignment
                (horizontalAlignment, cellBounds.getWidth(), clipWidth);
            final long alignmentY = RenderUtility.computeVerticalAlignment
                (verticalAlignment, cellBounds.getHeight(), clipHeight);
            cb = new StrictBounds(cellBounds.getX() + alignmentX,
                cellBounds.getY() + alignmentY,
                clipWidth,
                clipHeight);

            // Recompute the cells that this image will cover (now that it has been resized)
            rectangle = currentLayout.getTableBounds(cb, rectangle);


            pictureId = loadImageWithClipping(workbook, image, clipWidth, clipHeight, scaleFactor);
            if (pictureId <= 0)
            {
              return;
            }
          }
        }
      }
      catch (UnsupportedEncoderException uee)
      {
        // should not happen, as PNG is always supported.
        logger.warn("Assertation-Failure: PNG encoding failed.", uee);
        return;
      }


      final int cell1x = rectangle.getX1();
      final int cell1y = rectangle.getY1();
      final int cell2x = Math.max(cell1x, rectangle.getX2() - 1);
      final int cell2y = Math.max(cell1y, rectangle.getY2() - 1);

      final long cell1width = currentLayout.getCellWidth(cell1x);
      final long cell1height = currentLayout.getRowHeight(cell1y);
      final long cell2width = currentLayout.getCellWidth(cell2x);
      final long cell2height = currentLayout.getRowHeight(cell2y);

      final long cell1xPos = currentLayout.getXPosition(cell1x);
      final long cell1yPos = currentLayout.getYPosition(cell1y);
      final long cell2xPos = currentLayout.getXPosition(cell2x);
      final long cell2yPos = currentLayout.getYPosition(cell2y);

      final int dx1 = (int) (1023 * ((cb.getX() - cell1xPos) / (double) cell1width));
      final int dy1 = (int) (255 * ((cb.getY() - cell1yPos) / (double) cell1height));
      final int dx2 = (int) (1023 * ((cb.getX() + cb.getWidth() - cell2xPos) / (double) cell2width));
      final int dy2 = (int) (255 * ((cb.getY() + cb.getHeight() - cell2yPos) / (double) cell2height));

      final ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
      anchor.setDx1(dx1);
      anchor.setDy1(dy1);
      anchor.setDx2(dx2);
      anchor.setDy2(dy2);
      anchor.setCol1((short) cell1x);
      anchor.setRow1(cell1y);
      anchor.setCol2((short) cell2x);
      anchor.setRow2(cell2y);
      anchor.setAnchorType(ClientAnchor.MOVE_DONT_RESIZE); // Move, but don't size
      if (patriarch == null)
      {
        patriarch = sheet.createDrawingPatriarch();
      }
      final Picture picture = patriarch.createPicture(anchor, pictureId);
      ExcelPrinter.logger.info("Created image: " + pictureId + " => " + picture);
    }
    catch (IOException e)
    {
      ExcelPrinter.logger.warn("Failed to add image. Ignoring.", e);
    }
  }

  private int getImageFormat(final ResourceKey key)
  {
    final URL url = resourceManager.toURL(key);
    if (url == null)
    {
      return -1;
    }

    final String file = url.getFile();
    if (StringUtils.endsWithIgnoreCase(file, ".png"))
    {
      return Workbook.PICTURE_TYPE_PNG;
    }
    if (StringUtils.endsWithIgnoreCase(file, ".jpg") ||
        StringUtils.endsWithIgnoreCase(file, ".jpeg"))
    {
      return Workbook.PICTURE_TYPE_JPEG;
    }
    if (StringUtils.endsWithIgnoreCase(file, ".bmp") ||
        StringUtils.endsWithIgnoreCase(file, ".ico"))
    {
      return Workbook.PICTURE_TYPE_DIB;
    }
    return -1;
  }

  private int loadImageWithClipping(final Workbook workbook,
                                    final ImageContainer reference,
                                    final long clipWidth,
                                    final long clipHeight,
                                    final double deviceScaleFactor)
      throws IOException, UnsupportedEncoderException
  {

    Image image = null;
    // The image has an assigned URL ...
    if (reference instanceof URLImageContainer)
    {
      final URLImageContainer urlImage = (URLImageContainer) reference;
      final ResourceKey url = urlImage.getResourceKey();
      // if we have an source to load the image data from ..
      if (url != null && urlImage.isLoadable())
      {
        if (reference instanceof LocalImageContainer)
        {
          final LocalImageContainer li = (LocalImageContainer) reference;
          image = li.getImage();
        }
        if (image == null)
        {
          try
          {
            final Resource resource = resourceManager.create(url, null, Image.class);
            image = (Image) resource.getResource();
          }
          catch (ResourceException e)
          {
            // ignore.
          }
        }
      }
    }

    if (reference instanceof LocalImageContainer)
    {
      // Check, whether the imagereference contains an AWT image.
      // if so, then we can use that image instance for the recoding
      final LocalImageContainer li = (LocalImageContainer) reference;
      if (image == null)
      {
        image = li.getImage();
      }
    }

    if (image != null)
    {
      // now encode the image. We don't need to create digest data
      // for the image contents, as the image is perfectly identifyable
      // by its URL
      return clipAndEncodeImage(workbook, image, clipWidth, clipHeight, deviceScaleFactor);
    }
    return -1;
  }

  private int clipAndEncodeImage(final Workbook workbook,
                                 final Image image,
                                 final long width,
                                 final long height,
                                 final double deviceScaleFactor) throws UnsupportedEncoderException, IOException
  {
    final int imageWidth = (int) StrictGeomUtility.toExternalValue(width);
    final int imageHeight = (int) StrictGeomUtility.toExternalValue(height);
    // first clip.
    final BufferedImage bi = ImageUtils.createTransparentImage(imageWidth, imageHeight);
    final Graphics2D graphics = (Graphics2D) bi.getGraphics();
    graphics.scale(deviceScaleFactor, deviceScaleFactor);

    if (image instanceof BufferedImage)
    {
      if (graphics.drawImage(image, null, null) == false)
      {
        ExcelPrinter.logger.debug("Failed to render the image. This should not happen for BufferedImages");
      }
    }
    else
    {
      final WaitingImageObserver obs = new WaitingImageObserver(image);
      obs.waitImageLoaded();

      while (graphics.drawImage(image, null, obs) == false)
      {
        obs.waitImageLoaded();
        if (obs.isError())
        {
          ExcelPrinter.logger.warn("Error while loading the image during the rendering.");
          break;
        }
      }
    }

    graphics.dispose();
    final byte[] data = RenderUtility.encodeImage(bi);
    return workbook.addPicture(data, Workbook.PICTURE_TYPE_PNG);
  }

  private int loadImage(final Workbook workbook, final ImageContainer reference)
      throws IOException, UnsupportedEncoderException
  {
    Image image = null;
    // The image has an assigned URL ...
    if (reference instanceof URLImageContainer)
    {
      final URLImageContainer urlImage = (URLImageContainer) reference;
      final ResourceKey url = urlImage.getResourceKey();
      // if we have an source to load the image data from ..
      if (url != null && urlImage.isLoadable())
      {
        // and the image is one of the supported image formats ...
        // we we can embedd it directly ...
        final int format = getImageFormat(url);
        if (format == -1)
        {
          // This is a unsupported image format.
          if (reference instanceof LocalImageContainer)
          {
            final LocalImageContainer li = (LocalImageContainer) reference;
            image = li.getImage();
          }
          if (image == null)
          {
            try
            {
              final Resource resource = resourceManager.create(url, null, Image.class);
              image = (Image) resource.getResource();
            }
            catch (ResourceException re)
            {
              ExcelPrinter.logger.info("Failed to load image from URL " + url, re);
            }
          }
        }
        else
        {
          try
          {
            final ResourceData data = resourceManager.load(url);
            // create the image
            return workbook.addPicture(data.getResource(resourceManager), format);
          }
          catch (ResourceException re)
          {
            ExcelPrinter.logger.info("Failed to load image from URL " + url, re);
          }

        }
      }
    }

    if (reference instanceof LocalImageContainer)
    {
      // Check, whether the imagereference contains an AWT image.
      // if so, then we can use that image instance for the recoding
      final LocalImageContainer li = (LocalImageContainer) reference;
      if (image == null)
      {
        image = li.getImage();
      }
    }

    if (image != null)
    {
      // now encode the image. We don't need to create digest data
      // for the image contents, as the image is perfectly identifyable
      // by its URL
      final byte[] data = RenderUtility.encodeImage(image);
      return workbook.addPicture(data, Workbook.PICTURE_TYPE_PNG);
    }
    return -1;
  }

}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.modules.output.table.xls.helper.ExcelPrinter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.