Skip to content

Commit 519b423

Browse files
committed
Merge develop into master for 2.8.5 release
2 parents 7f3a2a4 + e7f337b commit 519b423

74 files changed

Lines changed: 3605 additions & 384 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ rcf/
174174
# Windows Store app package directories and files
175175
AppPackages/
176176
BundleArtifacts/
177+
# BenchmarkDotNet output
178+
**/BenchmarkDotNet.Artifacts/
177179
Package.StoreAssociation.xml
178180
_pkginfo.txt
179181

Directory.Build.props

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!--
2+
============================================================================
3+
Magicodes.IE 依赖治理规则 (Dependency Governance Contract)
4+
============================================================================
5+
6+
1. 版本走 props (Directory.Packages.props)
7+
- 禁止在任何 csproj 中显式写 PackageReference Version="x.y.z"
8+
- 所有版本信息只在 Directory.Packages.props 的 PackageVersion 里声明
9+
10+
2. csproj 只表达"哪些包"和"是否引入"
11+
- 不写 Version,版本由 CPM 决定
12+
- 按"是否引入"维度做 TFM 条件 ItemGroup (e.g. ns2.x vs net6+)
13+
- 禁止按"版本"维度做 TFM 条件 (那是 props 的工作)
14+
15+
3. CPM 默认启用 (ManagePackageVersionsCentrally=true)
16+
- 项目如需 opt-out,必须在 csproj 加注释说明原因
17+
- 当前合法的 opt-out: EPPlus (vendored 图像库,需按 TFM 多版本)
18+
19+
4. 传递依赖漏洞 pin 模式
20+
- props 里的 PackageVersion 不能强制提升传递版本
21+
- 在引入该传递依赖的 csproj 加顶层 PackageReference + 注释说明
22+
- 参考: Magicodes.IE.Excel.NPOI.csproj 的 System.Text.Encodings.Web pin
23+
24+
5. 公共属性集中在此文件
25+
- Version / Authors / Description / PackageTags 等打包元数据
26+
- NoWarn / 编译参数
27+
- 打包相关属性用 Condition="'$(IsPackable)' != 'false'" 包裹,避免影响测试项目
28+
============================================================================
29+
-->
30+
<Project>
31+
32+
<PropertyGroup>
33+
<Version>2.8.5</Version>
34+
<IncludeSymbols>true</IncludeSymbols>
35+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
36+
<NoWarn>1701;1702;CS1591;CS1573;1591;NU1507</NoWarn>
37+
<CheckEolTargetFramework>false</CheckEolTargetFramework>
38+
<NuGetAuditMode>direct</NuGetAuditMode>
39+
</PropertyGroup>
40+
41+
<PropertyGroup Condition="'$(IsPackable)' != 'false'">
42+
<Authors>雪雁</Authors>
43+
<Product>麦扣</Product>
44+
<PackageProjectUrl>https://docs.xin-lai.com/</PackageProjectUrl>
45+
<RepositoryUrl>https://github.com/dotnetcore/Magicodes.IE</RepositoryUrl>
46+
<PackageTags>Magicodes.IE;excel;xlsx;csv;word;pdf;html</PackageTags>
47+
<Description>
48+
Import and export general library, support Dto import and export, template export, fancy export and dynamic export, support Excel, Csv, Word, Pdf and Html.
49+
导入导出通用库,支持Dto导入导出、模板导出、花式导出以及动态导出,支持Excel、Csv、Word、Pdf和Html。
50+
51+
开源库地址:https://github.com/xin-lai
52+
博客地址:http://www.cnblogs.com/codelove/
53+
更新记录:https://github.com/dotnetcore/Magicodes.IE/blob/master/RELEASE.md
54+
公众号:麦扣聊技术
55+
交流QQ群:85318032
56+
57+
</Description>
58+
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
59+
</PropertyGroup>
60+
61+
<ItemGroup Condition="'$(IsPackable)' != 'false' AND '$(ManagePackageVersionsCentrally)' != 'false'">
62+
<PackageReference Include="Microsoft.SourceLink.GitHub">
63+
<PrivateAssets>all</PrivateAssets>
64+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
65+
</PackageReference>
66+
</ItemGroup>
67+
68+
</Project>

Directory.Packages.props

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<Project>
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
</PropertyGroup>
5+
6+
<ItemGroup>
7+
<PackageVersion Include="DynamicExpresso.Core" Version="2.3.3" />
8+
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
9+
<PackageVersion Include="CsvHelper" Version="27.2.1" />
10+
<PackageVersion Include="HtmlToOpenXml.dll" Version="3.1.0" />
11+
<PackageVersion Include="Haukcode.WkHtmlToPdfDotNet" Version="1.5.95" />
12+
<PackageVersion Include="Magicodes.RazorEngine.NetCore" Version="2.2.0" />
13+
<PackageVersion Include="Volo.Abp.Core" Version="4.4.0" />
14+
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
15+
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.1.1" />
16+
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="3.1.3" />
17+
<PackageVersion Include="GenFu" Version="1.6.0" />
18+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
19+
<PackageVersion Include="xunit" Version="2.4.2" />
20+
<PackageVersion Include="xunit.extensibility.core" Version="2.4.2" />
21+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
22+
<PackageVersion Include="Shouldly" Version="4.0.3" />
23+
<PackageVersion Include="Codeuctivity.ImageSharpCompare" Version="2.0.76" />
24+
<PackageVersion Include="coverlet.collector" Version="3.1.2" />
25+
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
26+
<PackageVersion Include="System.ComponentModel.Annotations" Version="4.7.0" />
27+
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
28+
<!-- Transitive CVE overrides (Stage 2 Step 6): see Excel.NPOI.csproj for context.
29+
Top-level PackageReference in the consumer csproj forces NuGet to honor these. -->
30+
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
31+
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.3" />
32+
<PackageVersion Include="NPOI" Version="2.8.0" />
33+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
34+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
35+
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
36+
<PackageVersion Include="MiniExcel" Version="1.29.0" />
37+
<PackageVersion Include="FakeItEasy" Version="7.2.0" />
38+
<PackageVersion Include="MSTest.TestAdapter" Version="2.2.7" />
39+
<PackageVersion Include="MSTest.TestFramework" Version="2.2.7" />
40+
<PackageVersion Include="System.Resources.Extensions" Version="5.0.0" />
41+
<PackageVersion Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
42+
<PackageVersion Include="CS-Script" Version="4.6.5" />
43+
</ItemGroup>
44+
45+
<ItemGroup>
46+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
47+
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="3.1.6" />
48+
</ItemGroup>
49+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
50+
<PackageVersion Update="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
51+
</ItemGroup>
52+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net6.0')) AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net7.0'))">
53+
<PackageVersion Update="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
54+
<PackageVersion Update="Microsoft.Extensions.DependencyModel" Version="6.0.0" />
55+
</ItemGroup>
56+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net7.0')) AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
57+
<PackageVersion Update="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
58+
<PackageVersion Update="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
59+
</ItemGroup>
60+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0')) AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net10.0'))">
61+
<PackageVersion Update="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
62+
<PackageVersion Update="Microsoft.Extensions.DependencyModel" Version="8.0.0" />
63+
</ItemGroup>
64+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net10.0'))">
65+
<PackageVersion Update="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
66+
<PackageVersion Update="Microsoft.Extensions.DependencyModel" Version="10.0.0" />
67+
</ItemGroup>
68+
</Project>

Magicodes.IE.sln

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ EndProject
1212
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7164789B-598A-46A6-BCBE-24CE10F93D53}"
1313
ProjectSection(SolutionItems) = preProject
1414
azure-pipelines.yml = azure-pipelines.yml
15-
common.props = common.props
1615
.github\workflows\docfx.yml = .github\workflows\docfx.yml
1716
.github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml
1817
README.md = README.md

NuGet.Config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33
<packageSources>
4-
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
4+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
55
</packageSources>
66
</configuration>

RELEASE.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,47 @@
11
# Release Log
22

3-
## 2.8.3
4-
**2025.12.24**
3+
## 2.8.5
4+
**2026.06.21**
5+
6+
### 性能
7+
- **图片导出大幅提速**:1000 张图片约 40s → 3s(约 13 倍)。从底层 EPPlus(SHA1 静态化、`wsDr` XPath 缓存、`Drawings.Preallocate`)到导出入口(按实际 drawing 数预分配、临时关闭 `DoAdjustDrawings``SKCodec` 跳过解码直接写入、同 URL 去重 + 并行下载)整条链路优化。
8+
- Linux 下 AutoFit 字体测量加速:预加载 `SKTypeface` 到缓存,避免每个单元格都调用 fontconfig。
9+
10+
### 修复
11+
- #549 Excel 缺少列时抛空指针异常,改为返回空值并生成错误信息
12+
- #617 多列表模板导出数据错乱(`updateCellWriters` 过滤条件 `RawRowStart``NewRowStart`
13+
- #585 CSV 导入空单元格映射为 null
14+
- #582 模板导入空行导致错误行号偏移
15+
- #588 新增 `DateOnly` / `DateOnly?` 类型导入支持
16+
- 图片下载无限挂起:`WebClient``HttpClient` + 30s 超时
17+
- 图片下载并发回归:上一条改动把 `WebClient`(per-call 新建)换成共享 `static HttpClient`,xUnit 跨 class 并行测试时并发下载偶发失败、图片静默丢失;改为 per-call `using new HttpClient` 隔离并发,超时保留
18+
- 导入 Stream 泄漏:改由 helper 托管生命周期,移除冗余 `GC.Collect`
19+
- `DoAdjustDrawings` 状态泄漏:EPPlus 内四处临时置 `false` 未用 try/finally 恢复,异常后 flag 永久卡死,导致后续 `Row.Height` / `Column.Width` setter 静默失效
20+
- macOS ARM64 PDF 导出崩溃(JSC JIT 的 W^X 冲突):加载原生库前设 `JSC_useJIT=0`
21+
- netstandard2.0/2.1 编译失败
22+
23+
### 新增
24+
- PDF 原生库抽象层:`PdfEnvironmentInfo` / `PdfExportOptions` / `IPdfNativeLibraryService.CheckEnvironment()` / `AddMagicodesPdfExporter()` DI 扩展,加载失败统一包装为 `InvalidOperationException`
25+
- macOS arm64 静态 wkhtmltopdf dylib(Qt 5.15 + QtWebKit 静态链接,arm64→x64 fallback)
26+
27+
### 依赖
28+
- NPOI 2.5.5 → 2.8.0,移除 `System.Drawing.Common`,SharpZipLib 1.4.2;消除 #554 / #557 / #561 传递依赖冲突与 #577 漏洞
29+
- `System.Linq.Dynamic.Core` 1.7.2、`Newtonsoft.Json` 统一 13.0.3
30+
- 引入 CPM(`Directory.Packages.props`)集中管理包版本,修掉历史版本漂移
31+
- 修复 NPOI 传递依赖 CVE:pin `System.Text.Encodings.Web` / `System.Security.Cryptography.Xml`
32+
33+
### 目标框架
34+
- 移除 net5.0 / netcoreapp3.1
35+
- EPPlusTest / Benchmarks 升至 net8.0
36+
37+
### CI
38+
- CI 改造为多平台矩阵(ubuntu/windows/macos)+ 新增 net9.0;新增兼容性策略文档与 `global.json`
39+
40+
### 测试
41+
- 新增图片导出性能基准(500/1000/2000 行)与 `DrawingsFlush` / `ImageOptimization` 回归测试
42+
43+
## 2.8.4
44+
**2026.05.30**
545

646
- 全面支持 .NET 10.0(LTS 版本)
747
- 更新所有核心库项目以支持 .NET 10.0 目标框架
@@ -10,7 +50,7 @@
1050
- 优化项目依赖包版本管理,统一 .NET 6.0+ 框架的包版本
1151

1252
## 2.8.2
13-
**2025.12.16**
53+
**2025.12.24**
1454

1555
- 升级 SkiaSharp 包从 2.88.6 到 3.119.1
1656
- 修复 SkiaSharp 3.x 中废弃的 API 使用问题,将 SKPaint.MeasureText 替换为 SKFont.MeasureText

common.props

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/EPPlus/EPPlus/Drawing/ExcelDrawingBase.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ public int RowOff
183183
const float STANDARD_DPI = 96;
184184
public const int EMU_PER_PIXEL = 9525;
185185
protected internal int _width = int.MinValue, _height = int.MinValue, _top = int.MinValue, _left = int.MinValue;
186-
bool _doNotAdjust = false;
186+
// Subclasses (e.g. ExcelPicture ctor) set this to skip GetPositionSize side effects.
187+
internal bool _doNotAdjust = false;
187188
internal ExcelDrawing(ExcelDrawings drawings, XmlNode node, string nameXPath) :
188189
base(drawings.NameSpaceManager, node)
189190
{
@@ -204,7 +205,13 @@ internal ExcelDrawing(ExcelDrawings drawings, XmlNode node, string nameXPath) :
204205
{
205206
To = null;
206207
}
207-
GetPositionSize();
208+
// Skip the initial GetPositionSize for newly-created nodes — they have no
209+
// xdr:from/xdr:to yet, so it would compute zeroed-out values. Reload path
210+
// (nodes from existing xml) still calls it to populate position fields.
211+
if (_topNode != null && _topNode.SelectSingleNode("xdr:from", drawings.NameSpaceManager) != null)
212+
{
213+
GetPositionSize();
214+
}
208215
_nameXPath = nameXPath;
209216
SchemaNodeOrder = new string[] { "from", "to", "graphicFrame", "sp", "clientData" };
210217
}

src/EPPlus/EPPlus/Drawing/ExcelDrawings.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class ExcelDrawings : IEnumerable<ExcelDrawing>, IDisposable
5151
private XmlDocument _drawingsXml = new XmlDocument();
5252
private Dictionary<string, int> _drawingNames;
5353
private List<ExcelDrawing> _drawings;
54+
// Cached //xdr:wsDr lookup; see WsDrNode getter for invalidation rules.
55+
private XmlNode _wsDrNode;
5456

5557
internal class ImageCompare
5658
{
@@ -105,6 +107,29 @@ internal ExcelDrawings(ExcelPackage xlPackage, ExcelWorksheet sheet)
105107

106108
internal ExcelWorksheet Worksheet { get; set; }
107109

110+
/// <summary>
111+
/// Pre-allocate capacity for the drawings collections. Call before adding many drawings
112+
/// to skip repeated dictionary/list resizing.
113+
/// </summary>
114+
/// <param name="estimatedCount">Expected total number of drawings to be added.</param>
115+
public void Preallocate(int estimatedCount)
116+
{
117+
if (estimatedCount <= 0) return;
118+
if (_drawingNames == null)
119+
{
120+
_drawingNames = new Dictionary<string, int>(estimatedCount, StringComparer.OrdinalIgnoreCase);
121+
_drawings = new List<ExcelDrawing>(estimatedCount);
122+
}
123+
else
124+
{
125+
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
126+
_drawingNames.EnsureCapacity(_drawingNames.Count + estimatedCount);
127+
#endif
128+
if (_drawings != null && _drawings.Capacity < _drawings.Count + estimatedCount)
129+
_drawings.Capacity = _drawings.Count + estimatedCount;
130+
}
131+
}
132+
108133
/// <summary>
109134
/// A reference to the drawing xml document
110135
/// </summary>
@@ -352,6 +377,27 @@ public ExcelPicture AddPicture(string Name, Image image, IImageFormat format, Ur
352377
throw (new Exception("AddPicture: Image can't be null"));
353378
}
354379

380+
/// <summary>
381+
/// 从原始字节添加图片,配合 SKCodec 读取的元数据使用
382+
/// </summary>
383+
public ExcelPicture AddPictureFromBytes(string name, byte[] imageBytes,
384+
string contentType, int width, int height)
385+
{
386+
if (imageBytes == null || imageBytes.Length == 0)
387+
throw new ArgumentException("imageBytes 不能为空");
388+
389+
if (_drawingNames.ContainsKey(name))
390+
throw new Exception("Name already exists in the drawings collection");
391+
392+
XmlElement drawNode = CreateDrawingXml();
393+
drawNode.SetAttribute("editAs", "oneCell");
394+
ExcelPicture pic = new ExcelPicture(this, drawNode, imageBytes, contentType, width, height);
395+
pic.Name = name;
396+
_drawings.Add(pic);
397+
_drawingNames.Add(name, _drawings.Count - 1);
398+
return pic;
399+
}
400+
355401
/// <summary>
356402
/// Add a picure to the worksheet
357403
/// </summary>
@@ -453,6 +499,20 @@ public ExcelShape AddShape(string Name, ExcelShape Source)
453499
return shape;
454500
}
455501

502+
// Cached lookup of the drawing root node. Re-resolved if the
503+
// underlying XmlDocument is replaced (Dispose nulls it out).
504+
private XmlNode WsDrNode
505+
{
506+
get
507+
{
508+
if (_wsDrNode == null || _wsDrNode.OwnerDocument != _drawingsXml)
509+
{
510+
_wsDrNode = _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
511+
}
512+
return _wsDrNode;
513+
}
514+
}
515+
456516
private XmlElement CreateDrawingXml()
457517
{
458518
if (DrawingXml.DocumentElement == null)
@@ -488,7 +548,7 @@ private XmlElement CreateDrawingXml()
488548
package.Flush();
489549
}
490550

491-
XmlNode colNode = _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
551+
XmlNode colNode = WsDrNode;
492552
XmlElement drawNode;
493553
if (this.Worksheet is ExcelChartsheet)
494554
{
@@ -666,6 +726,7 @@ internal void AdjustHeight(int[,] pos)
666726
public void Dispose()
667727
{
668728
_drawingsXml = null;
729+
_wsDrNode = null;
669730
_hashes.Clear();
670731
_hashes = null;
671732
_part = null;

0 commit comments

Comments
 (0)