diff --git a/ooxml/XSSF/Streaming/SXSSFDrawing.cs b/ooxml/XSSF/Streaming/SXSSFDrawing.cs new file mode 100644 index 000000000..d8a7402a1 --- /dev/null +++ b/ooxml/XSSF/Streaming/SXSSFDrawing.cs @@ -0,0 +1,67 @@ +/* ==================================================================== + 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. +==================================================================== */ + +namespace NPOI.XSSF.Streaming +{ + using NPOI.SS.UserModel; + using NPOI.XSSF.UserModel; + + /// + /// 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 : IDrawing + { + private SXSSFWorkbook _wb; + private XSSFDrawing _drawing; + + public SXSSFDrawing(SXSSFWorkbook workbook, XSSFDrawing Drawing) + { + this._wb = workbook; + this._drawing = Drawing; + } + public IPicture CreatePicture(IClientAnchor anchor, int pictureIndex) + { + XSSFPicture pict = (XSSFPicture)_drawing.CreatePicture(anchor, pictureIndex); + return new SXSSFPicture(_wb, pict); + } + public IComment CreateCellComment(IClientAnchor anchor) + { + return _drawing.CreateCellComment(anchor); + } + public IChart CreateChart(IClientAnchor anchor) + { + return _drawing.CreateChart(anchor); + } + public IClientAnchor 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); + } + //public ObjectData CreateObjectData(IClientAnchor anchor, int storageId, int pictureIndex) + //{ + // return _drawing.CreateObjectData(anchor, storageId, pictureIndex); + //} + //public Iterator iterator() + //{ + // return _drawing.Shapes.Iterator(); + //} + + } + +} + diff --git a/ooxml/XSSF/Streaming/SXSSFPicture.cs b/ooxml/XSSF/Streaming/SXSSFPicture.cs new file mode 100644 index 000000000..491611a03 --- /dev/null +++ b/ooxml/XSSF/Streaming/SXSSFPicture.cs @@ -0,0 +1,326 @@ +/* ==================================================================== + 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. +==================================================================== */ + +namespace NPOI.XSSF.Streaming +{ + using System.IO; + using NPOI.SS.UserModel; + using NPOI.SS.Util; + using NPOI.Util; + using NPOI.XSSF.UserModel; + using NPOI.OpenXml4Net.OPC; + using NPOI.OpenXmlFormats.Spreadsheet; + using NPOI.OpenXmlFormats.Dml; + using NPOI.OpenXmlFormats.Dml.Spreadsheet; + using SixLabors.ImageSharp; + + /// + /// + /// 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 class SXSSFPicture : IPicture + { + private static POILogger logger = POILogFactory.GetLogger(typeof(SXSSFPicture)); + /// + /// + /// 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 SXSSFWorkbook _wb; + private XSSFPicture _picture; + + public SXSSFPicture(SXSSFWorkbook _wb, XSSFPicture _picture) + { + this._wb = _wb; + this._picture = _picture; + } + + /// + /// + /// Return the underlying CTPicture bean that holds all properties for this picture + /// + /// + /// + /// + /// underlying CTPicture bean/// + + public CT_Picture 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. + ///

+ ///
+ ///
+ 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. + ///

+ ///
+ /// + /// + ///
+ /// 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. + /// + public void Resize(double scale) + { + XSSFClientAnchor anchor = (XSSFClientAnchor)ClientAnchor; + + XSSFClientAnchor pref = GetPreferredSize(scale); + + int row2 = anchor.Row1 + (pref.Row2 - pref.Row1); + int col2 = anchor.Col1 + (pref.Col2 - pref.Col1); + + anchor.Col2 = (/*setter*/col2); + anchor.Dx1 = (/*setter*/0); + anchor.Dx2 = (/*setter*/pref.Dx2); + + anchor.Row2 = (/*setter*/row2); + anchor.Dy1 = (/*setter*/0); + anchor.Dy2 = (/*setter*/pref.Dy2); + } + + /// + /// + /// Calculate the preferred size for this picture. + /// + /// + /// + /// + /// with the preferred size for this image/// + public IClientAnchor GetPreferredSize() + { + return GetPreferredSize(1.0); + } + + /// + /// + /// Calculate the preferred size for this picture. + /// + /// + /// + /// + /// the amount by which image dimensions are multiplied relative to the original size./// + /// with the preferred size for this image/// + public XSSFClientAnchor GetPreferredSize(double scale) + { + XSSFClientAnchor anchor = (XSSFClientAnchor)ClientAnchor; + + XSSFPictureData data = (XSSFPictureData)PictureData; + Size size = GetImageDimension(data.GetPackagePart(), data.PictureType); + double scaledWidth = size.Width * scale; + double scaledHeight = size.Height * scale; + + float w = 0; + int col2 = anchor.Col1 - 1; + + while (w <= scaledWidth) + { + w += GetColumnWidthInPixels(++col2); + } + + //assert(w > scaledWidth); + double cw = GetColumnWidthInPixels(col2); + double deltaW = w - scaledWidth; + int dx2 = (int)(XSSFShape.EMU_PER_PIXEL * (cw - deltaW)); + + anchor.Col2 = (/*setter*/col2); + anchor.Dx2 = (/*setter*/dx2); + + double h = 0; + int row2 = anchor.Row1 - 1; + + while (h <= scaledHeight) + { + h += GetRowHeightInPixels(++row2); + } + + //assert(h > scaledHeight); + double ch = GetRowHeightInPixels(row2); + double deltaH = h - scaledHeight; + int dy2 = (int)(XSSFShape.EMU_PER_PIXEL * (ch - deltaH)); + anchor.Row2 = (/*setter*/row2); + anchor.Dy2 = (/*setter*/dy2); + + CT_PositiveSize2D size2d = GetCTPicture().spPr.xfrm.ext; + size2d.cx = (/*setter*/(long)(scaledWidth * XSSFShape.EMU_PER_PIXEL)); + size2d.cy = (/*setter*/(long)(scaledHeight * XSSFShape.EMU_PER_PIXEL)); + + return anchor; + } + + private float GetColumnWidthInPixels(int columnIndex) + { + XSSFSheet sheet = (XSSFSheet)Sheet; + + CT_Col col = sheet.GetColumnHelper().GetColumn(columnIndex, false); + double numChars = col == null || !col.IsSetWidth() ? DEFAULT_COLUMN_WIDTH : col.width; + + 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 = (XSSFSheet)Sheet; + SXSSFSheet sheet = _wb.GetSXSSFSheet(xssfSheet); + IRow row = sheet.GetRow(rowIndex); + float height = row != null ? row.HeightInPoints : sheet.DefaultRowHeightInPoints; + return height * XSSFShape.PIXEL_DPI / XSSFShape.POINT_DPI; + } + /// + /// + /// Return the dimension of this image + /// + /// + /// + /// + /// the package part holding raw picture data/// + /// type of the picture: {@link Workbook#PICTURE_TYPE_JPEG}, + ///{@link Workbook#PICTURE_TYPE_PNG} or {@link Workbook#PICTURE_TYPE_DIB} + /// + /// + /// dimension in pixels/// + protected static Size GetImageDimension(PackagePart part, PictureType 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 Size(); + } + } + + /// + /// + /// Return picture data for this shape + /// + /// + /// + /// + /// data for this shape/// + public IPictureData PictureData + { + get + { + return _picture.PictureData; + } + } + + protected OpenXmlFormats.Dml.Spreadsheet.CT_ShapeProperties GetShapeProperties() + { + return GetCTPicture().spPr; + } + public XSSFAnchor GetAnchor() + { + return _picture.GetAnchor(); + } + public void Resize(double scaleX, double scaleY) + { + _picture.Resize(scaleX, scaleY); + } + public IClientAnchor GetPreferredSize(double scaleX, double scaleY) + { + return _picture.GetPreferredSize(scaleX, scaleY); + } + public Size GetImageDimension() + { + return _picture.GetImageDimension(); + } + public IClientAnchor ClientAnchor + { + get + { + XSSFAnchor a = GetAnchor(); + return (a is XSSFClientAnchor) ? (XSSFClientAnchor)a : null; + } + } + + public XSSFDrawing GetDrawing() + { + return _picture.GetDrawing(); + } + public ISheet Sheet + { + get + { + return _picture.Sheet; + } + } + public string GetShapeName() + { + return _picture.Name; + } + public IShape GetParent() + { + return _picture.Parent; + } + public bool IsNoFill + { + get { return _picture.IsNoFill; } + set { _picture.IsNoFill = value;} + } + + public void SetFillColor(int red, int green, int blue) + { + _picture.SetFillColor(red, green, blue); + } + public void SetLineStyleColor(int red, int green, int blue) + { + _picture.SetLineStyleColor(red, green, blue); + } + } +} + diff --git a/ooxml/XSSF/Streaming/SXSSFSheet.cs b/ooxml/XSSF/Streaming/SXSSFSheet.cs index 8f80deb7d..cef184e79 100644 --- a/ooxml/XSSF/Streaming/SXSSFSheet.cs +++ b/ooxml/XSSF/Streaming/SXSSFSheet.cs @@ -652,7 +652,7 @@ public List GetHyperlinkList() public IDrawing CreateDrawingPatriarch() { - return _sh.CreateDrawingPatriarch(); + return new SXSSFDrawing((SXSSFWorkbook)Workbook, (XSSFDrawing)_sh.CreateDrawingPatriarch()); } public void CreateFreezePane(int colSplit, int rowSplit) diff --git a/ooxml/XSSF/Streaming/SXSSFWorkbook.cs b/ooxml/XSSF/Streaming/SXSSFWorkbook.cs index 888355792..b97425a49 100644 --- a/ooxml/XSSF/Streaming/SXSSFWorkbook.cs +++ b/ooxml/XSSF/Streaming/SXSSFWorkbook.cs @@ -386,7 +386,7 @@ private void DeregisterSheetMapping(XSSFSheet xSheet) } - private XSSFSheet GetXSSFSheet(SXSSFSheet sheet) + public XSSFSheet GetXSSFSheet(SXSSFSheet sheet) { if (sheet != null && _sxFromXHash.ContainsKey(sheet)) return _sxFromXHash[sheet]; @@ -394,7 +394,7 @@ private XSSFSheet GetXSSFSheet(SXSSFSheet sheet) return null; } - private SXSSFSheet GetSXSSFSheet(XSSFSheet sheet) + public SXSSFSheet GetSXSSFSheet(XSSFSheet sheet) { if (sheet != null && _xFromSxHash.ContainsKey(sheet)) return _xFromSxHash[sheet]; diff --git a/testcases/main/HSSF/UserModel/TestHSSFWorkbook.cs b/testcases/main/HSSF/UserModel/TestHSSFWorkbook.cs index 866addf61..bab4e007a 100644 --- a/testcases/main/HSSF/UserModel/TestHSSFWorkbook.cs +++ b/testcases/main/HSSF/UserModel/TestHSSFWorkbook.cs @@ -1474,6 +1474,13 @@ public void TestWriteToNewFile() wb.Close(); } + [Test] + [Ignore("poi")] + public override void CreateDrawing() + { + base.CreateDrawing(); + // the dimensions for this image are different than for XSSF and SXSSF + } [Test] public void TestBug854() diff --git a/testcases/main/SS/UserModel/BaseTestWorkbook.cs b/testcases/main/SS/UserModel/BaseTestWorkbook.cs index 5838dbd67..77e656b7a 100644 --- a/testcases/main/SS/UserModel/BaseTestWorkbook.cs +++ b/testcases/main/SS/UserModel/BaseTestWorkbook.cs @@ -24,6 +24,7 @@ namespace TestCases.SS.UserModel using NUnit.Framework; using System; using System.Collections; + using System.IO; using System.Text; using TestCases.HSSF; using TestCases.SS; @@ -943,6 +944,63 @@ public void AddSheetTwice() wb.Close(); } + // bug 51233 and 55075: correctly size image if Added to a row with a custom height + [Test] + public virtual void CreateDrawing() + { + + IWorkbook wb = _testDataProvider.CreateWorkbook(); + ISheet sheet = wb.CreateSheet("Main Sheet"); + IRow row0 = sheet.CreateRow(0); + IRow row1 = sheet.CreateRow(1); + ICell cell = row1.CreateCell(0); + row0.CreateCell(1); + row1.CreateCell(0); + row1.CreateCell(1); + + byte[] pictureData = _testDataProvider.GetTestDataFileContent("logoKarmokar4.png"); + + int handle = wb.AddPicture(pictureData, PictureType.PNG); + IDrawing Drawing = sheet.CreateDrawingPatriarch(); + ICreationHelper helper = wb.GetCreationHelper(); + IClientAnchor anchor = helper.CreateClientAnchor(); + anchor.AnchorType = (/*setter*/AnchorType.DontMoveAndResize); + anchor.Col1 = (/*setter*/0); + anchor.Row1 = (/*setter*/0); + IPicture picture = Drawing.CreatePicture(anchor, handle); + + row0.HeightInPoints = (/*setter*/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 + Assert.AreEqual(0, anchor.Row1); + Assert.AreEqual(0, anchor.Row2); + Assert.AreEqual(0, anchor.Dy1); + Assert.AreEqual(1609725, anchor.Dy2); //HSSF: 225 + + // Check Drawing width + Assert.AreEqual(0, anchor.Col1); + Assert.AreEqual(0, anchor.Col2); + Assert.AreEqual(0, anchor.Dx1); + Assert.AreEqual(1114425, anchor.Dx2); //HSSF: 171 + + bool WriteOut = false; + if (WriteOut) + { + string ext = "." + _testDataProvider.StandardFileNameExtension; + string prefix = wb.GetType().Name + "-CreateDrawing"; + FileInfo f = TempFile.CreateTempFile(prefix, ext); + FileStream out1 = new FileStream(f.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite); + wb.Write(out1); + out1.Close(); + } + wb.Close(); + } + } } \ No newline at end of file