| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.xssf.streaming; |
| |
| import java.awt.Dimension; |
| import java.io.IOException; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.openxml4j.opc.PackagePart; |
| import org.apache.poi.ss.usermodel.Picture; |
| import org.apache.poi.ss.usermodel.Row; |
| import org.apache.poi.ss.usermodel.Shape; |
| import org.apache.poi.ss.usermodel.Sheet; |
| import org.apache.poi.ss.usermodel.Workbook; |
| import org.apache.poi.ss.util.ImageUtils; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.Units; |
| import org.apache.poi.xssf.usermodel.XSSFAnchor; |
| import org.apache.poi.xssf.usermodel.XSSFClientAnchor; |
| import org.apache.poi.xssf.usermodel.XSSFDrawing; |
| import org.apache.poi.xssf.usermodel.XSSFPicture; |
| import org.apache.poi.xssf.usermodel.XSSFPictureData; |
| import org.apache.poi.xssf.usermodel.XSSFSheet; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture; |
| import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol; |
| |
| /** |
| * Streaming version of Picture. |
| * Most of the code is a copy of the non-streaming XSSFPicture code. |
| * This is necessary as a private method getRowHeightInPixels of that class needs to be changed, which is called by a method call chain nested several levels. |
| * |
| * The main change is to access the rows in the SXSSF sheet, not the always empty rows in the XSSF sheet when checking the row heights. |
| */ |
| public final class SXSSFPicture implements Picture { |
| private static final Logger LOG = LogManager.getLogger(SXSSFPicture.class); |
| /** |
| * Column width measured as the number of characters of the maximum digit width of the |
| * numbers 0, 1, 2, ..., 9 as rendered in the normal style's font. There are 4 pixels of margin |
| * padding (two on each side), plus 1 pixel padding for the gridlines. |
| * |
| * This value is the same for default font in Office 2007 (Calibri) and Office 2003 and earlier (Arial) |
| */ |
| private static final float DEFAULT_COLUMN_WIDTH = 9.140625f; |
| |
| private final SXSSFWorkbook _wb; |
| private final XSSFPicture _picture; |
| |
| SXSSFPicture(SXSSFWorkbook _wb, XSSFPicture _picture) { |
| this._wb = _wb; |
| this._picture = _picture; |
| } |
| |
| /** |
| * Return the underlying CTPicture bean that holds all properties for this picture |
| * |
| * @return the underlying CTPicture bean |
| */ |
| @Internal |
| public CTPicture getCTPicture(){ |
| return _picture.getCTPicture(); |
| } |
| |
| /** |
| * Reset the image to the original size. |
| * |
| * <p> |
| * Please note, that this method works correctly only for workbooks |
| * with the default font size (Calibri 11pt for .xlsx). |
| * If the default font is changed the resized image can be streched vertically or horizontally. |
| * </p> |
| */ |
| @Override |
| public void resize(){ |
| resize(1.0); |
| } |
| |
| /** |
| * Reset the image to the original size. |
| * <p> |
| * Please note, that this method works correctly only for workbooks |
| * with the default font size (Calibri 11pt for .xlsx). |
| * If the default font is changed the resized image can be streched vertically or horizontally. |
| * </p> |
| * |
| * @param scale the amount by which image dimensions are multiplied relative to the original size. |
| * <code>resize(1.0)</code> sets the original size, <code>resize(0.5)</code> resize to 50% of the original, |
| * <code>resize(2.0)</code> resizes to 200% of the original. |
| */ |
| @Override |
| public void resize(double scale){ |
| XSSFClientAnchor anchor = getClientAnchor(); |
| XSSFClientAnchor pref = getPreferredSize(scale); |
| if (anchor == null || pref == null) { |
| LOG.atWarn().log("picture is not anchored via client anchor - ignoring resize call"); |
| return; |
| } |
| |
| int row2 = anchor.getRow1() + (pref.getRow2() - pref.getRow1()); |
| int col2 = anchor.getCol1() + (pref.getCol2() - pref.getCol1()); |
| |
| anchor.setCol2(col2); |
| anchor.setDx1(0); |
| anchor.setDx2(pref.getDx2()); |
| |
| anchor.setRow2(row2); |
| anchor.setDy1(0); |
| anchor.setDy2(pref.getDy2()); |
| } |
| |
| /** |
| * Calculate the preferred size for this picture. |
| * |
| * @return XSSFClientAnchor with the preferred size for this image |
| */ |
| @Override |
| public XSSFClientAnchor getPreferredSize(){ |
| return getPreferredSize(1.0); |
| } |
| |
| /** |
| * Calculate the preferred size for this picture. |
| * |
| * @param scale the amount by which image dimensions are multiplied relative to the original size. |
| * @return XSSFClientAnchor with the preferred size for this image |
| */ |
| public XSSFClientAnchor getPreferredSize(double scale){ |
| XSSFClientAnchor anchor = getClientAnchor(); |
| if (anchor == null) { |
| LOG.atWarn().log("picture is not anchored via client anchor - ignoring resize call"); |
| return null; |
| } |
| |
| XSSFPictureData data = getPictureData(); |
| Dimension size = getImageDimension(data.getPackagePart(), data.getPictureType()); |
| double scaledWidth = size.getWidth() * scale; |
| double scaledHeight = size.getHeight() * scale; |
| |
| float w = 0; |
| int col2 = anchor.getCol1()-1; |
| |
| while (w <= scaledWidth) { |
| w += getColumnWidthInPixels(++col2); |
| } |
| |
| assert (w > scaledWidth); |
| double cw = getColumnWidthInPixels(col2); |
| double deltaW = w - scaledWidth; |
| int dx2 = (int)(Units.EMU_PER_PIXEL * (cw - deltaW)); |
| |
| anchor.setCol2(col2); |
| anchor.setDx2(dx2); |
| |
| double h = 0; |
| int row2 = anchor.getRow1()-1; |
| |
| while (h <= scaledHeight) { |
| h += getRowHeightInPixels(++row2); |
| } |
| |
| assert (h > scaledHeight); |
| double ch = getRowHeightInPixels(row2); |
| double deltaH = h - scaledHeight; |
| int dy2 = (int)(Units.EMU_PER_PIXEL * (ch - deltaH)); |
| anchor.setRow2(row2); |
| anchor.setDy2(dy2); |
| |
| CTPositiveSize2D size2d = getCTPicture().getSpPr().getXfrm().getExt(); |
| size2d.setCx((long)(scaledWidth * Units.EMU_PER_PIXEL)); |
| size2d.setCy((long)(scaledHeight * Units.EMU_PER_PIXEL)); |
| |
| return anchor; |
| } |
| |
| private float getColumnWidthInPixels(int columnIndex){ |
| XSSFSheet sheet = getSheet(); |
| |
| CTCol col = sheet.getColumnHelper().getColumn(columnIndex, false); |
| double numChars = col == null || !col.isSetWidth() ? DEFAULT_COLUMN_WIDTH : col.getWidth(); |
| |
| return (float)numChars*Units.DEFAULT_CHARACTER_WIDTH; |
| } |
| |
| private float getRowHeightInPixels(int rowIndex) { |
| // THE FOLLOWING THREE LINES ARE THE MAIN CHANGE compared to the non-streaming version: use the SXSSF sheet, |
| // not the XSSF sheet (which never contais rows when using SXSSF) |
| XSSFSheet xssfSheet = getSheet(); |
| SXSSFSheet sxSheet = _wb.getSXSSFSheet(xssfSheet); |
| Sheet sheet = sxSheet == null ? xssfSheet : sxSheet; |
| Row row = sheet.getRow(rowIndex); |
| float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); |
| return height * Units.PIXEL_DPI / Units.POINT_DPI; |
| } |
| /** |
| * Return the dimension of this image |
| * |
| * @param part the package part holding raw picture data |
| * @param type type of the picture: {@link Workbook#PICTURE_TYPE_JPEG}, |
| * {@link Workbook#PICTURE_TYPE_PNG} or {@link Workbook#PICTURE_TYPE_DIB} |
| * |
| * @return image dimension in pixels |
| */ |
| protected static Dimension getImageDimension(PackagePart part, int type){ |
| try { |
| return ImageUtils.getImageDimension(part.getInputStream(), type); |
| } catch (IOException e){ |
| //return a "singulariry" if ImageIO failed to read the image |
| LOG.atWarn().withThrowable(e).log("Failed to read image"); |
| return new Dimension(); |
| } |
| } |
| |
| /** |
| * Return picture data for this shape |
| * |
| * @return picture data for this shape |
| */ |
| @Override |
| public XSSFPictureData getPictureData() { |
| return _picture.getPictureData(); |
| } |
| |
| protected CTShapeProperties getShapeProperties(){ |
| return getCTPicture().getSpPr(); |
| } |
| |
| @Override |
| public XSSFAnchor getAnchor() { |
| return _picture.getAnchor(); |
| } |
| |
| @Override |
| public void resize(double scaleX, double scaleY) { |
| _picture.resize(scaleX, scaleY); |
| } |
| |
| @Override |
| public XSSFClientAnchor getPreferredSize(double scaleX, double scaleY) { |
| return _picture.getPreferredSize(scaleX, scaleY); |
| } |
| |
| @Override |
| public Dimension getImageDimension() { |
| return _picture.getImageDimension(); |
| } |
| |
| @Override |
| public XSSFClientAnchor getClientAnchor() { |
| XSSFAnchor a = getAnchor(); |
| return (a instanceof XSSFClientAnchor) ? (XSSFClientAnchor)a : null; |
| } |
| |
| public XSSFDrawing getDrawing() { |
| return _picture.getDrawing(); |
| } |
| |
| @Override |
| public XSSFSheet getSheet() { |
| return _picture.getSheet(); |
| } |
| |
| @Override |
| public String getShapeName() { |
| return _picture.getShapeName(); |
| } |
| |
| @Override |
| public Shape getParent() { |
| return _picture.getParent(); |
| } |
| |
| @Override |
| public boolean isNoFill() { |
| return _picture.isNoFill(); |
| } |
| |
| @Override |
| public void setNoFill(boolean noFill) { |
| _picture.setNoFill(noFill); |
| } |
| |
| @Override |
| public void setFillColor(int red, int green, int blue) { |
| _picture.setFillColor(red, green, blue); |
| } |
| |
| @Override |
| public void setLineStyleColor( int red, int green, int blue ) { |
| _picture.setLineStyleColor(red, green, blue); |
| } |
| } |