Skip to content

Commit 08102d1

Browse files
shps951023gitee-org
authored andcommitted
!14 Fix the issue with image insertion method when exporting Excel using MiniExcel, and add support for embedding images into cells.
Merge pull request !14 from wsp/feature/fix-picture-insertion-embedding
2 parents 21d73f2 + deb3113 commit 08102d1

File tree

6 files changed

+345
-24
lines changed

6 files changed

+345
-24
lines changed

samples/xlsx/TestImageType.xlsx

10.9 KB
Binary file not shown.

src/MiniExcel/Enums/XlsxImgType.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
3+
namespace MiniExcelLibs.Enums;
4+
5+
/// <summary>
6+
/// Excel 图片展示方式(是否随单元格对齐/缩放)。
7+
/// </summary>
8+
public enum XlsxImgType
9+
{
10+
/// <summary>
11+
/// 图片随单元格移动但不缩放(OneCellAnchor)。
12+
/// 通常用于图片只绑定一个起点单元格。
13+
/// </summary>
14+
OneCellAnchor,
15+
/// <summary>
16+
/// 图片浮动在表格上,固定位置不随单元格变化(AbsoluteAnchor)。
17+
/// </summary>
18+
AbsoluteAnchor,
19+
/// <summary>
20+
/// 图片嵌入单元格中,随单元格移动和缩放(TwoCellAnchor)。
21+
/// </summary>
22+
TwoCellAnchor,
23+
24+
}

src/MiniExcel/Picture/MiniExcelPicture.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using MiniExcelLibs.Utils;
1+
using MiniExcelLibs.Enums;
2+
using MiniExcelLibs.Utils;
3+
using System.Drawing;
24

35
namespace MiniExcelLibs.Picture;
46

@@ -8,7 +10,11 @@ public class MiniExcelPicture
810
public string? SheetName { get; set; }
911
public string? PictureType { get; set; }
1012
public string? CellAddress { get; set; }
11-
13+
/// <summary>
14+
/// 只有当图片处于AbsoluteAnchor浮动才会生效
15+
/// </summary>
16+
public Point Location { get; set; }
17+
public XlsxImgType ImgType { get; set; }
1218
internal int ColumnNumber => ReferenceHelper.ConvertCellToXY(CellAddress).Item1 -1;
1319
internal int RowNumber => ReferenceHelper.ConvertCellToXY(CellAddress).Item2 - 1;
1420

src/MiniExcel/Picture/MiniExcelPictureImplement.cs

Lines changed: 213 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
using System;
1+
using MiniExcelLibs.Enums;
2+
using MiniExcelLibs.OpenXml;
3+
using MiniExcelLibs.Zip;
4+
using System;
5+
using System.Drawing;
26
using System.IO;
37
using System.IO.Compression;
48
using System.Linq;
59
using System.Threading;
610
using System.Threading.Tasks;
711
using System.Xml;
8-
using MiniExcelLibs.OpenXml;
9-
using MiniExcelLibs.Zip;
1012
using Zomp.SyncMethodGenerator;
1113

1214
namespace MiniExcelLibs.Picture;
@@ -140,9 +142,10 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c
140142
var row = image.RowNumber;
141143
var widthPx = image.WidthPx;
142144
var heightPx = image.HeightPx;
143-
144-
// Step 1: Add image to /xl/media/
145-
var imageName = $"image{Guid.NewGuid():N}.png";
145+
var imgtype = image.ImgType;
146+
var location = image.Location;
147+
// Step 1: Add image to /xl/media/
148+
var imageName = $"image{Guid.NewGuid():N}.png";
146149
var imagePath = $"xl/media/{imageName}";
147150
var imageEntry = archive.CreateEntry(imagePath);
148151

@@ -167,7 +170,7 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c
167170

168171
// Step 3: Add anchor to drawing XML
169172
var relId = $"rId{Guid.NewGuid():N}";
170-
drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId);
173+
drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype,location);
171174

172175
// Step 4: Add image relationship to drawing rels
173176
var relNode = drawingRelsDoc.CreateElement("Relationship", drawingRelsDoc.DocumentElement.NamespaceURI);
@@ -288,6 +291,8 @@ public static void AddPicture(Stream excelStream, params MiniExcelPicture[] imag
288291
var row = image.RowNumber;
289292
var widthPx = image.WidthPx;
290293
var heightPx = image.HeightPx;
294+
var imgtype = image.ImgType;
295+
var location = image.Location;
291296
// Step 1: Add image to /xl/media/
292297
var imageName = $"image{Guid.NewGuid():N}.png";
293298
var imagePath = $"xl/media/{imageName}";
@@ -312,7 +317,7 @@ public static void AddPicture(Stream excelStream, params MiniExcelPicture[] imag
312317

313318
// Step 3: Add anchor to drawing XML
314319
var relId = $"rId{Guid.NewGuid():N}";
315-
drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId);
320+
drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype,location);
316321

317322
// Step 4: Add image relationship to drawing rels
318323
var relNode = drawingRelsDoc.CreateElement("Relationship", drawingRelsDoc.DocumentElement.NamespaceURI);
@@ -364,12 +369,16 @@ private static XmlNamespaceManager GetNamespaceManager(XmlDocument doc)
364369
return nsmgr;
365370
}
366371

367-
private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, int row, int widthPx, int heightPx, string relId)
368-
{
369-
return DrawingXmlHelper.CreateOrUpdateDrawingXml(existingDoc, col, row, widthPx, heightPx, relId);
370-
}
372+
private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, int row, int widthPx, int heightPx, string relId)
373+
{
374+
return DrawingXmlHelper.CreateOrUpdateDrawingXml(existingDoc, col, row, widthPx, heightPx, relId);
375+
}
376+
private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, int row, int widthPx, int heightPx, string relId,XlsxImgType imgtype,Point location)
377+
{
378+
return DrawingXmlHelper.CreateOrUpdateDrawingXml(existingDoc, col, row, widthPx, heightPx, relId,imgtype, location);
379+
}
371380

372-
public class DrawingXmlHelper
381+
public class DrawingXmlHelper
373382
{
374383
private const string XdrNamespace = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
375384
private const string ANamespace = "http://schemas.openxmlformats.org/drawingml/2006/main";
@@ -389,8 +398,198 @@ private static string GetColumnName(int colIndex)
389398

390399
return columnName;
391400
}
401+
public static XmlDocument CreateOrUpdateDrawingXml(
402+
XmlDocument? existingDoc,
403+
int col, int row,
404+
int widthPx, int heightPx,
405+
string relId,
406+
XlsxImgType imgType,
407+
Point Location
408+
)
409+
{
410+
var doc = existingDoc ?? new XmlDocument();
411+
var ns = new XmlNamespaceManager(doc.NameTable);
412+
ns.AddNamespace("xdr", XdrNamespace);
413+
ns.AddNamespace("a", ANamespace);
414+
ns.AddNamespace("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
415+
416+
// Root
417+
XmlElement wsDr;
418+
if (existingDoc is null)
419+
{
420+
wsDr = doc.CreateElement("xdr", "wsDr", XdrNamespace);
421+
wsDr.SetAttribute("xmlns:xdr", XdrNamespace);
422+
wsDr.SetAttribute("xmlns:a", ANamespace);
423+
doc.AppendChild(wsDr);
424+
}
425+
else
426+
{
427+
wsDr = doc.DocumentElement!;
428+
}
429+
430+
XmlNodeList anchors = wsDr.SelectNodes("//xdr:oneCellAnchor | //xdr:twoCellAnchor | //xdr:absoluteAnchor", ns);
431+
int imageCount = anchors?.Count ?? 0;
432+
int nextId = imageCount + 2;
433+
434+
string anchorType = imgType switch
435+
{
436+
XlsxImgType.AbsoluteAnchor => "absoluteAnchor",
437+
XlsxImgType.TwoCellAnchor => "twoCellAnchor",
438+
XlsxImgType.OneCellAnchor => "oneCellAnchor",
439+
_ => "oneCellAnchor"
440+
};
441+
442+
var anchor = doc.CreateElement("xdr", anchorType, XdrNamespace);
443+
if (imgType == XlsxImgType.TwoCellAnchor)
444+
anchor.SetAttribute("editAs", "twoCell");
445+
446+
if (imgType == XlsxImgType.AbsoluteAnchor)
447+
{
448+
449+
450+
var pos = doc.CreateElement("xdr", "pos", XdrNamespace);
451+
pos.SetAttribute("x", PixelsToEmu(Location.X).ToString()); // 使用实际列宽
452+
pos.SetAttribute("y", PixelsToEmu(Location.Y).ToString()); // 使用实际行高
453+
454+
var ext = doc.CreateElement("xdr", "ext", XdrNamespace);
455+
ext.SetAttribute("cx", PixelsToEmu(widthPx).ToString());
456+
ext.SetAttribute("cy", PixelsToEmu(heightPx).ToString());
457+
458+
anchor.AppendChild(pos);
459+
anchor.AppendChild(ext);
460+
461+
}
462+
else if (imgType == XlsxImgType.TwoCellAnchor)
463+
{
464+
var from = doc.CreateElement("xdr", "from", XdrNamespace);
465+
AppendXmlElement(doc, from, "xdr", "col", col.ToString());
466+
AppendXmlElement(doc, from, "xdr", "colOff", "0");
467+
AppendXmlElement(doc, from, "xdr", "row", row.ToString());
468+
AppendXmlElement(doc, from, "xdr", "rowOff", "0");
469+
var to = doc.CreateElement("xdr", "to", XdrNamespace);
470+
AppendXmlElement(doc, to, "xdr", "col", (col + 1).ToString());
471+
AppendXmlElement(doc, to, "xdr", "colOff", "0");
472+
AppendXmlElement(doc, to, "xdr", "row", (row + 1).ToString());
473+
AppendXmlElement(doc, to, "xdr", "rowOff", "0");
474+
475+
anchor.AppendChild(from);
476+
anchor.AppendChild(to);
477+
}
478+
else // OneCellAnchor
479+
{
480+
var from = doc.CreateElement("xdr", "from", XdrNamespace);
481+
AppendXmlElement(doc, from, "xdr", "col", col.ToString());
482+
AppendXmlElement(doc, from, "xdr", "colOff", "0");
483+
AppendXmlElement(doc, from, "xdr", "row", row.ToString());
484+
AppendXmlElement(doc, from, "xdr", "rowOff", "0");
485+
var to = doc.CreateElement("xdr", "to", XdrNamespace);
486+
AppendXmlElement(doc, to, "xdr", "col", (col ).ToString()); // Adjust the column and row for size
487+
AppendXmlElement(doc, to, "xdr", "colOff", "0");
488+
AppendXmlElement(doc, to, "xdr", "row", (row ).ToString());
489+
AppendXmlElement(doc, to, "xdr", "rowOff", "0");
490+
491+
var ext = doc.CreateElement("xdr", "ext", XdrNamespace);
492+
ext.SetAttribute("cx", PixelsToEmu(widthPx).ToString());
493+
ext.SetAttribute("cy", PixelsToEmu(heightPx).ToString());
494+
495+
anchor.AppendChild(from);
496+
anchor.AppendChild(ext);
497+
}
498+
499+
// -------- Image Content --------
500+
// <xdr:pic>
501+
var pic = doc.CreateElement("xdr", "pic", XdrNamespace);
502+
503+
// <xdr:nvPicPr>
504+
var nvPicPr = doc.CreateElement("xdr", "nvPicPr", XdrNamespace);
505+
var cNvPr = doc.CreateElement("xdr", "cNvPr", XdrNamespace);
506+
cNvPr.SetAttribute("id", nextId.ToString());
507+
cNvPr.SetAttribute("name", $"ImageAt{GetColumnName(col)}{row + 1}");
508+
509+
// <a:extLst>...<a16:creationId ... />
510+
var extLst = doc.CreateElement("a", "extLst", ANamespace);
511+
var extNode = doc.CreateElement("a", "ext", ANamespace);
512+
extNode.SetAttribute("uri", "{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}");
513+
514+
var creationId = doc.CreateElement("a16", "creationId", "http://schemas.microsoft.com/office/drawing/2014/main");
515+
creationId.SetAttribute("id", "http://schemas.microsoft.com/office/drawing/2014/main", $"{{00000000-0008-0000-0000-0000{nextId:D6}000000}}");
516+
517+
extNode.AppendChild(creationId);
518+
extLst.AppendChild(extNode);
519+
cNvPr.AppendChild(extLst);
520+
521+
// <xdr:cNvPicPr><a:picLocks noChangeAspect="1" /></xdr:cNvPicPr>
522+
var cNvPicPr = doc.CreateElement("xdr", "cNvPicPr", XdrNamespace);
523+
var picLocks = doc.CreateElement("a", "picLocks", ANamespace);
524+
picLocks.SetAttribute("noChangeAspect", "1");
525+
cNvPicPr.AppendChild(picLocks);
526+
527+
nvPicPr.AppendChild(cNvPr);
528+
nvPicPr.AppendChild(cNvPicPr);
529+
pic.AppendChild(nvPicPr);
530+
531+
// <xdr:blipFill>
532+
var blipFill = doc.CreateElement("xdr", "blipFill", XdrNamespace);
533+
var blip = doc.CreateElement("a", "blip", ANamespace);
534+
535+
blip.SetAttribute("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
536+
blip.SetAttribute("embed", ns.LookupNamespace("r"), relId);
537+
blip.SetAttribute("cstate", "print");
538+
539+
var stretch = doc.CreateElement("a", "stretch", ANamespace);
540+
var fillRect = doc.CreateElement("a", "fillRect", ANamespace);
541+
stretch.AppendChild(fillRect);
542+
543+
blipFill.AppendChild(blip);
544+
blipFill.AppendChild(stretch);
545+
pic.AppendChild(blipFill);
546+
547+
// <xdr:spPr>
548+
var spPr = doc.CreateElement("xdr", "spPr", XdrNamespace);
549+
var xfrm = doc.CreateElement("a", "xfrm", ANamespace);
550+
551+
var off = doc.CreateElement("a", "off", ANamespace);
552+
off.SetAttribute("x", "0");
553+
off.SetAttribute("y", "0");
554+
555+
//var spExt = doc.CreateElement("a", "ext", ANamespace);
556+
//spExt.SetAttribute("cx", "0");
557+
//spExt.SetAttribute("cy", "0");
558+
559+
xfrm.AppendChild(off);
560+
//xfrm.AppendChild(spExt);
561+
562+
var prstGeom = doc.CreateElement("a", "prstGeom", ANamespace);
563+
prstGeom.SetAttribute("prst", "rect");
564+
565+
var avLst = doc.CreateElement("a", "avLst", ANamespace);
566+
prstGeom.AppendChild(avLst);
567+
568+
spPr.AppendChild(xfrm);
569+
spPr.AppendChild(prstGeom);
570+
571+
pic.AppendChild(spPr);
572+
573+
// <xdr:clientData />
574+
var clientData = doc.CreateElement("xdr", "clientData", XdrNamespace);
575+
576+
//oneCellAnchor.AppendChild(from);
577+
//oneCellAnchor.AppendChild(ext);
578+
//oneCellAnchor.AppendChild(pic);
579+
//oneCellAnchor.AppendChild(clientData);
580+
581+
//wsDr.AppendChild(oneCellAnchor);
582+
//var pic = CreatePictureNode(doc, col, row, widthPx, heightPx, relId, nextId);
583+
// var clientData = doc.CreateElement("xdr", "clientData", XdrNamespace);
584+
585+
anchor.AppendChild(pic);
586+
anchor.AppendChild(clientData);
587+
wsDr.AppendChild(anchor);
392588

393-
public static XmlDocument CreateOrUpdateDrawingXml(
589+
return doc;
590+
}
591+
592+
public static XmlDocument CreateOrUpdateDrawingXml(
394593
XmlDocument? existingDoc,
395594
int col, int row,
396595
int widthPx, int heightPx,

tests/MiniExcelTests/MiniExcelIssueTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using MiniExcelLibs.Picture;
2222
using TableStyles = MiniExcelLibs.OpenXml.TableStyles;
2323
using System.Threading.Tasks;
24+
using LicenseContext = OfficeOpenXml.LicenseContext;
2425

2526
namespace MiniExcelLibs.Tests;
2627

@@ -4428,9 +4429,8 @@ public void TestIssue814()
44284429
MiniExcel.AddPicture(path.FilePath, images);
44294430

44304431
using var package = new ExcelPackage(new FileInfo(path.FilePath));
4431-
4432-
// Check picture in the first sheet (C3)
4433-
var firstSheet = package.Workbook.Worksheets[0];
4432+
// Check picture in the first sheet (C3)
4433+
var firstSheet = package.Workbook.Worksheets[0];
44344434
var pictureInC3 = firstSheet.Drawings.OfType<OfficeOpenXml.Drawing.ExcelPicture>().FirstOrDefault(p => p.From.Column == 2 && p.From.Row == 2);
44354435
Assert.NotNull(pictureInC3);
44364436

@@ -4479,8 +4479,9 @@ public void TestIssue815()
44794479
];
44804480

44814481
MiniExcel.AddPicture(path.FilePath, images);
4482+
//ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
44824483

4483-
using (var package = new ExcelPackage(new FileInfo(path.FilePath)))
4484+
using (var package = new ExcelPackage(new FileInfo(path.FilePath)))
44844485
{
44854486
// Check picture in the first sheet (C3)
44864487
var firstSheet = package.Workbook.Worksheets[0];
@@ -4538,8 +4539,9 @@ public void TestIssue816()
45384539
];
45394540

45404541
MiniExcel.AddPicture(path.FilePath, images);
4542+
//ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
45414543

4542-
using var package = new ExcelPackage(new FileInfo(path.FilePath));
4544+
using var package = new ExcelPackage(new FileInfo(path.FilePath));
45434545

45444546
// Check picture in the first sheet (C3)
45454547
var firstSheet = package.Workbook.Worksheets[0];

0 commit comments

Comments
 (0)