From 61f00a509fa58f59df3fd1ff1d36e4e7a4cd22ba Mon Sep 17 00:00:00 2001 From: Javen O'Neal Date: Sun, 9 Oct 2016 10:02:36 +0000 Subject: [PATCH] bug 55075,51233: create drawings with correct height when rows are custom height git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1763950 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/poi/util/OOXMLLite.java | 1 - .../poi/xssf/streaming/SXSSFDrawing.java | 62 ++++ .../poi/xssf/streaming/SXSSFPicture.java | 274 ++++++++++++++++++ .../apache/poi/xssf/streaming/SXSSFSheet.java | 2 +- .../poi/hssf/usermodel/TestHSSFWorkbook.java | 9 + .../poi/ss/usermodel/BaseTestWorkbook.java | 58 ++++ 6 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java diff --git a/src/ooxml/java/org/apache/poi/util/OOXMLLite.java b/src/ooxml/java/org/apache/poi/util/OOXMLLite.java index afb60f600e..5d783e0878 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 0000000000..2bb226caca --- /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 0000000000..eaabbb1ec6 --- /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 e217dd2e6c..2c9fb6e372 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 07fd7910f8..55e1d77db6 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 52f542281a..2c7c15add6 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(); + } } -- 2.39.5