diff --git a/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML.csproj index d5ddc65..0539fcd 100644 --- a/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML.csproj @@ -72,6 +72,10 @@ + + + + @@ -343,4 +347,4 @@ --> - \ No newline at end of file + diff --git a/ClosedXML/Excel/Drawings/IXLMarker.cs b/ClosedXML/Excel/Drawings/IXLMarker.cs new file mode 100644 index 0000000..fd52192 --- /dev/null +++ b/ClosedXML/Excel/Drawings/IXLMarker.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ClosedXML.Excel.Drawings +{ + public interface IXLMarker + { + Int32 ColumnId { get; set; } + Int32 RowId { get; set; } + Double ColumnOffset { get; set; } + Double RowOffset { get; set; } + + /// + /// Get the zero-based column number. + /// + Int32 GetZeroBasedColumn(); + + /// + /// Get the zero-based row number. + /// + Int32 GetZeroBasedRow(); + } +} diff --git a/ClosedXML/Excel/Drawings/IXLPicture.cs b/ClosedXML/Excel/Drawings/IXLPicture.cs new file mode 100644 index 0000000..84829b7 --- /dev/null +++ b/ClosedXML/Excel/Drawings/IXLPicture.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DocumentFormat.OpenXml.Packaging; + +namespace ClosedXML.Excel.Drawings +{ + public interface IXLPicture + { + Stream ImageStream { get; set; } + + List GetMarkers(); + void AddMarker(IXLMarker marker); + + long MaxHeight { get; set; } + long MaxWidth { get; set; } + long Width { get; set; } + long Height { get; set; } + long OffsetX { get; set; } + long OffsetY { get; set; } + long RawOffsetX { get; set; } + long RawOffsetY { get; set; } + bool IsAbsolute { get; set; } + + /// + /// Type of image. The supported formats are defined by OpenXML's ImagePartType. + /// Default value is "jpeg" + /// + String Type { get; set; } + + String Name { get; set; } + + /// + /// Get the enum representation of the Picture type. + /// + ImagePartType GetImagePartType(); + } +} diff --git a/ClosedXML/Excel/Drawings/XLMarker.cs b/ClosedXML/Excel/Drawings/XLMarker.cs new file mode 100644 index 0000000..1ed01be --- /dev/null +++ b/ClosedXML/Excel/Drawings/XLMarker.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ClosedXML.Excel.Drawings +{ + public class XLMarker : IXLMarker + { + private Int32 colId; + private Int32 rowId; + private Double colOffset; + private Double rowOffset; + + public Int32 ColumnId + { + set + { + if (value < 1 || value > XLHelper.MaxColumnNumber) + throw new ArgumentOutOfRangeException(String.Format("Column number must be between 1 and {0}", + XLHelper.MaxColumnNumber)); + this.colId = value; + } + get + { + return this.colId; + } + } + + public Int32 RowId + { + set + { + if (value < 1 || value > XLHelper.MaxRowNumber) + throw new ArgumentOutOfRangeException(String.Format("Row number must be between 1 and {0}", + XLHelper.MaxRowNumber)); + this.rowId = value; + } + get + { + return this.rowId; + } + } + + public Double ColumnOffset + { + set + { + this.colOffset = value; + } + get + { + return this.colOffset; + } + } + + public Double RowOffset + { + set + { + this.rowOffset = value; + } + get + { + return this.rowOffset; + } + } + + public Int32 GetZeroBasedColumn() + { + return colId - 1; + } + + public Int32 GetZeroBasedRow() + { + return rowId - 1; + } + } +} diff --git a/ClosedXML/Excel/Drawings/XLPicture.cs b/ClosedXML/Excel/Drawings/XLPicture.cs new file mode 100644 index 0000000..0d54b51 --- /dev/null +++ b/ClosedXML/Excel/Drawings/XLPicture.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DocumentFormat.OpenXml.Packaging; + +namespace ClosedXML.Excel.Drawings +{ + public class XLPicture : IXLPicture + { + private MemoryStream imgStream; + private List Markers; + private String name; + private bool isAbsolute; + private ImagePartType type = ImagePartType.Jpeg; + + private long iMaxWidth = 500; + private long iMaxHeight = 500; + + private long iWidth; + private long iHeight; + + private long iOffsetX; + private long iOffsetY; + + private float iVerticalResolution; + private float iHorizontalResolution; + + private bool isResized = false; + + private void Resize() + { + if (iWidth > iMaxHeight || iHeight > iMaxWidth) + { + var scaleX = (double)iWidth / (double)iMaxWidth; + var scaleY = (double)iHeight / (double)iMaxHeight; + var scale = Math.Max(scaleX, scaleY); + iWidth = (int)((double)iWidth / scale); + iHeight = (int)((double)iHeight / scale); + } + isResized = true; + } + + public long MaxWidth + { + get + { + return ConvertToEmu(iMaxWidth, iHorizontalResolution); + } + set + { + iMaxWidth = value; + isResized = false; + } + } + + + public long MaxHeight + { + get + { + return ConvertToEmu(iMaxHeight, iVerticalResolution); + } + set + { + iMaxHeight = value; + isResized = false; + } + } + + public long Width + { + get + { + if (!isResized) + { + Resize(); + } + return ConvertToEmu(iWidth, iHorizontalResolution); + } + set { } + } + + public long Height + { + get + { + if (!isResized) + { + Resize(); + } + return ConvertToEmu(iHeight, iVerticalResolution); + } + set { } + } + + public long RawHeight + { + get { return (long)iHeight; } + } + public long RawWidth + { + get { return (long)iWidth; } + } + + public long OffsetX + { + get { return ConvertToEmu(iOffsetX, iHorizontalResolution); } + set { iOffsetX = value; } + } + public long OffsetY + { + get { return ConvertToEmu(iOffsetY, iVerticalResolution); } + set { iOffsetY = value; } + } + + public long RawOffsetX + { + get + { + return iOffsetX; + } + set + { + iOffsetX = value; + } + } + + public long RawOffsetY + { + get + { + return iOffsetY; + } + set + { + iOffsetY = value; + } + } + + private long ConvertToEmu(long pixels, float resolution) + { + return (long)(914400 * pixels / resolution); + } + + public Stream ImageStream + { + get + { + return imgStream; + } + set + { + if (imgStream == null) + { + imgStream = new MemoryStream(); + } + else + { + imgStream.Dispose(); + imgStream = new MemoryStream(); + } + value.CopyTo(imgStream); + imgStream.Seek(0, SeekOrigin.Begin); + + using (var bitmap = new System.Drawing.Bitmap(imgStream)) + { + iWidth = (long)bitmap.Width; + iHeight = (long)bitmap.Height; + iHorizontalResolution = bitmap.HorizontalResolution; + iVerticalResolution = bitmap.VerticalResolution; + } + imgStream.Seek(0, SeekOrigin.Begin); + } + } + + public List GetMarkers() + { + return Markers != null ? Markers : new List(); + } + public void AddMarker(IXLMarker marker) + { + if (Markers == null) + { + Markers = new List(); + } + Markers.Add(marker); + } + + public String Name + { + get + { + return name; + } + set + { + name = value; + } + } + + public bool IsAbsolute + { + get + { + return isAbsolute; + } + set + { + isAbsolute = value; + } + } + + public String Type + { + get + { + return GetExtension(type); + } + set + { + try + { + type = (ImagePartType)Enum.Parse(typeof(ImagePartType), value, true); + } + catch + { + type = ImagePartType.Jpeg; + } + } + } + + private String GetExtension(ImagePartType type) + { + return type.ToString().ToLower(); + } + + public ImagePartType GetImagePartType() + { + return type; + } + } +} diff --git a/ClosedXML/Excel/IXLWorksheet.cs b/ClosedXML/Excel/IXLWorksheet.cs index 0d9dcd6..cf5b21b 100644 --- a/ClosedXML/Excel/IXLWorksheet.cs +++ b/ClosedXML/Excel/IXLWorksheet.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace ClosedXML.Excel { @@ -285,7 +286,7 @@ IXLTables Tables { get; } /// - /// Copies the + /// Copies the /// /// /// @@ -368,5 +369,13 @@ Object Evaluate(String expression); String Author { get; set; } + + List Pictures(); + + /// + /// Adds image to worksheet + /// + /// XLPicture object to be added. + void AddPicture(Drawings.XLPicture pic); } } diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs index 9a99f83..9b099a8 100644 --- a/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/Excel/XLWorkbook_Save.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.CustomProperties; using DocumentFormat.OpenXml.Drawing; @@ -34,6 +35,7 @@ using Text = DocumentFormat.OpenXml.Spreadsheet.Text; using TopBorder = DocumentFormat.OpenXml.Spreadsheet.TopBorder; using Underline = DocumentFormat.OpenXml.Spreadsheet.Underline; +using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet; using System.Xml; using System.Xml.Linq; using System.Text; @@ -2501,6 +2503,184 @@ return stroke; } + private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture) + { + var drawingsPart = worksheetPart.DrawingsPart ?? + worksheetPart.AddNewPart( + GeneratePartId(picture.Name, worksheetPart)); + + if (drawingsPart.WorksheetDrawing == null) + { + drawingsPart.WorksheetDrawing = new Xdr.WorksheetDrawing(); + } + + var worksheetDrawing = drawingsPart.WorksheetDrawing; + + var imagePart = drawingsPart.AddImagePart(picture.GetImagePartType(), + GeneratePartId(picture.Name, drawingsPart)); + + using (Stream stream = new MemoryStream()) + { + picture.ImageStream.CopyTo(stream); + stream.Seek(0, SeekOrigin.Begin); + imagePart.FeedData(stream); + } + + var extentsCx = picture.Width; + var extentsCy = picture.Height; + + var nvps = worksheetDrawing.Descendants(); + var nvpId = nvps.Count() > 0 ? + (UInt32Value)worksheetDrawing.Descendants().Max(p => p.Id.Value) + 1 : + 1U; + if (picture.IsAbsolute) + { + Xdr.AbsoluteAnchor absoluteAnchor; + absoluteAnchor = new Xdr.AbsoluteAnchor( + new Xdr.Position + { + X = picture.OffsetX, + Y = picture.OffsetY + }, + new Xdr.Extent + { + Cx = extentsCx, + Cy = extentsCy + }, + new Xdr.Picture( + new Xdr.NonVisualPictureProperties( + new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name }, + new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true }) + ), + new Xdr.BlipFill( + new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print }, + new Stretch(new FillRectangle()) + ), + new Xdr.ShapeProperties( + new Transform2D( + new Offset { X = 0, Y = 0 }, + new Extents { Cx = extentsCx, Cy = extentsCy } + ), + new PresetGeometry { Preset = ShapeTypeValues.Rectangle } + ) + ), + new Xdr.ClientData() + ); + + worksheetDrawing.Append(absoluteAnchor); + } + else + { + var markers = picture.GetMarkers(); + Xdr.FromMarker fMark; + Xdr.ToMarker tMark; + if (markers.Count == 2) + { + fMark = new Xdr.FromMarker + { + ColumnId = new Xdr.ColumnId(markers[0].GetZeroBasedColumn().ToString()), + RowId = new Xdr.RowId(markers[0].GetZeroBasedRow().ToString()), + ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.OffsetX).ToString()), + RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.OffsetY).ToString()) + }; + tMark = new Xdr.ToMarker + { + ColumnId = new Xdr.ColumnId(markers[1].GetZeroBasedColumn().ToString()), + RowId = new Xdr.RowId(markers[1].GetZeroBasedRow().ToString()), + ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.OffsetX).ToString()), + RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.OffsetY).ToString()) + }; + + Xdr.TwoCellAnchor cellAnchor; + cellAnchor = new Xdr.TwoCellAnchor( + fMark, + tMark, + new Xdr.Picture( + new Xdr.NonVisualPictureProperties( + new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name }, + new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true }) + ), + new Xdr.BlipFill( + new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print }, + new Stretch(new FillRectangle()) + ), + new Xdr.ShapeProperties( + new Transform2D( + new Offset { X = 0, Y = 0 }, + new Extents { Cx = extentsCx, Cy = extentsCy } + ), + new PresetGeometry { Preset = ShapeTypeValues.Rectangle } + ) + ), + new Xdr.ClientData() + ); + + worksheetDrawing.Append(cellAnchor); + } + else if (markers.Count == 1) + { + fMark = new Xdr.FromMarker + { + ColumnId = new Xdr.ColumnId(markers[0].GetZeroBasedColumn().ToString()), + RowId = new Xdr.RowId(markers[0].GetZeroBasedRow().ToString()), + ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.OffsetX).ToString()), + RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.OffsetY).ToString()) + }; + + Xdr.OneCellAnchor cellAnchor; + cellAnchor = new Xdr.OneCellAnchor( + fMark, + new Xdr.Extent + { + Cx = extentsCx, + Cy = extentsCy + }, + new Xdr.Picture( + new Xdr.NonVisualPictureProperties( + new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name }, + new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true }) + ), + new Xdr.BlipFill( + new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print }, + new Stretch(new FillRectangle()) + ), + new Xdr.ShapeProperties( + new Transform2D( + new Offset { X = 0, Y = 0 }, + new Extents { Cx = extentsCx, Cy = extentsCy } + ), + new PresetGeometry { Preset = ShapeTypeValues.Rectangle } + ) + ), + new Xdr.ClientData() + ); + + worksheetDrawing.Append(cellAnchor); + } + } + } + + private static Regex embedRegex = new Regex("[^a-zA-Z0-9]"); + + public static string GeneratePartId(string name, OpenXmlPart oxp) + { + var partId = name ?? "rId1"; + partId = embedRegex.Replace(partId, ""); + + // We guarantee the id uniqueness based off the name + try + { + oxp.GetPartById(partId); + } + catch(ArgumentOutOfRangeException) + { + return partId; + } + + partId += "c"; + return GeneratePartId(partId, oxp); + } + private static Vml.TextBox GetTextBox(IXLDrawingStyle ds) { var sb = new StringBuilder(); @@ -4564,19 +4744,6 @@ #endregion - #region Drawings - - //worksheetPart.Worksheet.RemoveAllChildren(); - //{ - // OpenXmlElement previousElement = cm.GetPreviousElementFor(XLWSContentManager.XLWSContents.Drawing); - // worksheetPart.Worksheet.InsertAfter(new Drawing() { Id = String.Format("rId{0}", 1) }, previousElement); - //} - - //Drawing drawing = worksheetPart.Worksheet.Elements().First(); - //cm.SetElement(XLWSContentManager.XLWSContents.Drawing, drawing); - - #endregion - #region Tables worksheetPart.Worksheet.RemoveAllChildren(); @@ -4596,6 +4763,26 @@ #endregion + #region Drawings + + var pics = xlWorksheet.Pictures(); + if (pics != null) + { + foreach (Drawings.IXLPicture pic in pics) + { + AddPictureAnchor(worksheetPart, pic); + } + } + + if (xlWorksheet.Pictures() != null && xlWorksheet.Pictures().Count > 0) + { + Drawing worksheetDrawing = new Drawing { Id = worksheetPart.GetIdOfPart(worksheetPart.DrawingsPart) }; + worksheetDrawing.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"); + worksheetPart.Worksheet.InsertBefore(worksheetDrawing, tableParts); + } + + #endregion + #region LegacyDrawing if (xlWorksheet.LegacyDrawingIsNew) diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs index 227ee49..132ec9d 100644 --- a/ClosedXML/Excel/XLWorksheet.cs +++ b/ClosedXML/Excel/XLWorksheet.cs @@ -96,6 +96,7 @@ public Boolean LegacyDrawingIsNew; private Double _columnWidth; public XLWorksheetInternals Internals { get; private set; } + private List pictures; public override IEnumerable Styles { @@ -1491,5 +1492,19 @@ } public String Author { get; set; } + + public List Pictures() + { + return pictures; + } + + public void AddPicture(Drawings.XLPicture pic) + { + if (pictures == null) + { + pictures = new List(); + } + pictures.Add(pic); + } } } diff --git a/ClosedXML_Examples/ClosedXML_Examples.csproj b/ClosedXML_Examples/ClosedXML_Examples.csproj index 2daeac4..de7cb8e 100644 --- a/ClosedXML_Examples/ClosedXML_Examples.csproj +++ b/ClosedXML_Examples/ClosedXML_Examples.csproj @@ -44,6 +44,9 @@ ClosedXML.snk + + true + ..\packages\DocumentFormat.OpenXml.2.7.1\lib\net45\DocumentFormat.OpenXml.dll @@ -74,6 +77,8 @@ + + @@ -174,6 +179,12 @@ ClosedXML + + + + + +