diff --git a/src/ooxml/java/org/apache/poi/util/OOXMLLite.java b/src/ooxml/java/org/apache/poi/util/OOXMLLite.java index afb60f600..5d783e087 100644 --- a/src/ooxml/java/org/apache/poi/util/OOXMLLite.java +++ b/src/ooxml/java/org/apache/poi/util/OOXMLLite.java @@ -98,7 +98,6 @@ public final class OOXMLLite { "BaseTestXCell", "BaseTestXSSFPivotTable", "TestSXSSFWorkbook\\$\\d", - "TestSXSSFWorkbook\\$NullOutputStream", "TestUnfixedBugs", "MemoryUsage", "TestDataProvider", diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java new file mode 100644 index 000000000..2bb226cac --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java @@ -0,0 +1,62 @@ +/* ==================================================================== + 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 org.apache.poi.ss.usermodel.Chart; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; + +/** + * Streaming version of Drawing. + * Delegates most tasks to the non-streaming XSSF code. + * TODO: Potentially, Comment and Chart need a similar streaming wrapper like Picture. + */ +public class SXSSFDrawing implements Drawing { + private final SXSSFWorkbook _wb; + private final XSSFDrawing _drawing; + + public SXSSFDrawing(SXSSFWorkbook workbook, XSSFDrawing drawing) { + this._wb = workbook; + this._drawing = drawing; + } + + @Override + public SXSSFPicture createPicture(ClientAnchor anchor, int pictureIndex) { + XSSFPicture pict = _drawing.createPicture(anchor, pictureIndex); + return new SXSSFPicture(_wb, pict); + } + + @Override + public Comment createCellComment(ClientAnchor anchor) { + return _drawing.createCellComment(anchor); + } + + @Override + public Chart createChart(ClientAnchor anchor) { + return _drawing.createChart(anchor); + } + + @Override + public ClientAnchor createAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2) { + return _drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2); + } +} + diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java new file mode 100644 index 000000000..eaabbb1ec --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java @@ -0,0 +1,274 @@ +/* ==================================================================== + 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 org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Picture; +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.util.ImageUtils; +import org.apache.poi.util.Internal; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.apache.poi.xssf.usermodel.*; +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; + +import java.awt.*; +import java.io.IOException; + +/** + * 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 POILogger logger = POILogFactory.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 (Calibry) and Office 2003 and earlier (Arial) + */ + private static 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. + * + *
+ * 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. + *
+ */ + @Override + public void resize(){ + resize(1.0); + } + + /** + * Reset the image to the original size. + *+ * 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. + *
+ * + * @param scale the amount by which image dimensions are multiplied relative to the original size. + *resize(1.0)
sets the original size, resize(0.5)
resize to 50% of the original,
+ * resize(2.0)
resizes to 200% of the original.
+ */
+ @Override
+ public void resize(double scale){
+ XSSFClientAnchor anchor = getAnchor();
+
+ XSSFClientAnchor pref = getPreferredSize(scale);
+
+ 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 = getAnchor();
+
+ 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();
+ int dx2 = 0;
+
+ for (;;) {
+ w += getColumnWidthInPixels(col2);
+ if(w > scaledWidth) break;
+ col2++;
+ }
+
+ if(w > scaledWidth) {
+ double cw = getColumnWidthInPixels(col2 );
+ double delta = w - scaledWidth;
+ dx2 = (int)(XSSFShape.EMU_PER_PIXEL * (cw - delta));
+ }
+ anchor.setCol2(col2);
+ anchor.setDx2(dx2);
+
+ double h = 0;
+ int row2 = anchor.getRow1();
+ int dy2 = 0;
+
+ for (;;) {
+ h += getRowHeightInPixels(row2);
+ if(h > scaledHeight) break;
+ row2++;
+ }
+
+ if(h > scaledHeight) {
+ double ch = getRowHeightInPixels(row2);
+ double delta = h - scaledHeight;
+ dy2 = (int)(XSSFShape.EMU_PER_PIXEL * (ch - delta));
+ }
+ anchor.setRow2(row2);
+ anchor.setDy2(dy2);
+
+ CTPositiveSize2D size2d = getCTPicture().getSpPr().getXfrm().getExt();
+ size2d.setCx((long)(scaledWidth * XSSFShape.EMU_PER_PIXEL));
+ size2d.setCy((long)(scaledHeight * XSSFShape.EMU_PER_PIXEL));
+
+ return anchor;
+ }
+
+ private float getColumnWidthInPixels(int columnIndex){
+ XSSFSheet sheet = getParent();
+
+ CTCol col = sheet.getColumnHelper().getColumn(columnIndex, false);
+ double numChars = col == null || !col.isSetWidth() ? DEFAULT_COLUMN_WIDTH : col.getWidth();
+
+ return (float)numChars*XSSFWorkbook.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 = getParent();
+ SXSSFSheet sheet = _wb.getSXSSFSheet(xssfSheet);
+ Row row = sheet.getRow(rowIndex);
+ float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints();
+ return height * XSSFShape.PIXEL_DPI / XSSFShape.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
+ logger.log(POILogger.WARN, e);
+ 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();
+ }
+
+ private XSSFSheet getParent() {
+ return (XSSFSheet)_picture.getDrawing().getParent();
+ }
+
+ private XSSFClientAnchor getAnchor() {
+ return (XSSFClientAnchor)_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();
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
index e217dd2e6..2c9fb6e37 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
@@ -1704,7 +1704,7 @@ public class SXSSFSheet implements Sheet
@Override
public Drawing createDrawingPatriarch()
{
- return _sh.createDrawingPatriarch();
+ return new SXSSFDrawing((SXSSFWorkbook)getWorkbook(), _sh.createDrawingPatriarch());
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java
index 07fd7910f..55e1d77db 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java
@@ -73,6 +73,7 @@ import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.RecordFormatException;
import org.apache.poi.util.TempFile;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -1315,4 +1316,12 @@ public final class TestHSSFWorkbook extends BaseTestWorkbook {
assertEquals(3, wb.getNumberOfSheets());
wb.close();
}
+
+ @Ignore
+ @Test
+ @Override
+ public void createDrawing() throws Exception {
+ super.createDrawing();
+ // the dimensions for this image are different than for XSSF and SXSSF
+ }
}
diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java
index 52f542281..2c7c15add 100644
--- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java
+++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java
@@ -26,15 +26,19 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
+import org.apache.poi.POIDataSamples;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.ss.ITestDataProvider;
+import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.NullOutputStream;
+import org.apache.poi.util.TempFile;
import org.junit.Test;
public abstract class BaseTestWorkbook {
@@ -895,5 +899,59 @@ public abstract class BaseTestWorkbook {
wb.close();
}
+
+ // bug 51233 and 55075: correctly size image if added to a row with a custom height
+ @Test
+ public void createDrawing() throws Exception {
+ Workbook wb = _testDataProvider.createWorkbook();
+ Sheet sheet = wb.createSheet("Main Sheet");
+ Row row0 = sheet.createRow(0);
+ Row row1 = sheet.createRow(1);
+ Cell cell = row1.createCell(0);
+ row0.createCell(1);
+ row1.createCell(0);
+ row1.createCell(1);
+
+ byte[] pictureData = _testDataProvider.getTestDataFileContent("logoKarmokar4.png");
+
+ int handle = wb.addPicture(pictureData, Workbook.PICTURE_TYPE_PNG);
+ Drawing drawing = sheet.createDrawingPatriarch();
+ CreationHelper helper = wb.getCreationHelper();
+ ClientAnchor anchor = helper.createClientAnchor();
+ anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE);
+ anchor.setCol1(0);
+ anchor.setRow1(0);
+ Picture picture = drawing.createPicture(anchor, handle);
+
+ row0.setHeightInPoints(144);
+ // set a column width so that XSSF and SXSSF have the same width (default widths may be different otherwise)
+ sheet.setColumnWidth(0, 100*256);
+ picture.resize();
+
+ // The actual dimensions don't matter as much as having XSSF and SXSSF produce the same size drawings
+
+ // Check drawing height
+ assertEquals(0, anchor.getRow1());
+ assertEquals(0, anchor.getRow2());
+ assertEquals(0, anchor.getDy1());
+ assertEquals(1609725, anchor.getDy2()); //HSSF: 225
+
+ // Check drawing width
+ assertEquals(0, anchor.getCol1());
+ assertEquals(0, anchor.getCol2());
+ assertEquals(0, anchor.getDx1());
+ assertEquals(1114425, anchor.getDx2()); //HSSF: 171
+
+ final boolean writeOut = false;
+ if (writeOut) {
+ String ext = "." + _testDataProvider.getStandardFileNameExtension();
+ String prefix = wb.getClass().getName() + "-createDrawing";
+ File f = TempFile.createTempFile(prefix, ext);
+ FileOutputStream out = new FileOutputStream(f);
+ wb.write(out);
+ out.close();
+ }
+ wb.close();
+ }
}