From bad05f77b59d3dd1b7462263fa01a416afa117d0 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Sun, 5 Apr 2026 18:42:06 +0800
Subject: [PATCH] =?UTF-8?q?improve:pdf=E6=8F=92=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Controls/PdfEmbeddedView.cs | 2 +-
Ink Canvas/Helpers/PdfDocumentRenderHelper.cs | 62 +++++++
Ink Canvas/Helpers/PdfToImageMitHelper.cs | 96 ++++++++++
Ink Canvas/InkCanvasForClass.csproj | 1 +
.../MainWindow_cs/MW_ElementsControls.cs | 4 +-
Ink Canvas/packages.lock.json | 175 ++++++++++++++++++
6 files changed, 337 insertions(+), 3 deletions(-)
create mode 100644 Ink Canvas/Helpers/PdfDocumentRenderHelper.cs
create mode 100644 Ink Canvas/Helpers/PdfToImageMitHelper.cs
diff --git a/Ink Canvas/Controls/PdfEmbeddedView.cs b/Ink Canvas/Controls/PdfEmbeddedView.cs
index 6c4d21f7..5885a37a 100644
--- a/Ink Canvas/Controls/PdfEmbeddedView.cs
+++ b/Ink Canvas/Controls/PdfEmbeddedView.cs
@@ -102,7 +102,7 @@ namespace Ink_Canvas.Controls
NotifyPageNavigationStateChanged();
try
{
- BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
+ BitmapSource raw = await PdfDocumentRenderHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
if (raw == null)
return;
diff --git a/Ink Canvas/Helpers/PdfDocumentRenderHelper.cs b/Ink Canvas/Helpers/PdfDocumentRenderHelper.cs
new file mode 100644
index 00000000..2a42383a
--- /dev/null
+++ b/Ink Canvas/Helpers/PdfDocumentRenderHelper.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// PDF 页数与位图渲染:优先 (Windows.Data.Pdf),失败或无效结果时使用 PDFtoImage(MIT) 备用。
+ ///
+ internal static class PdfDocumentRenderHelper
+ {
+ public static async Task GetPageCountAsync(string pdfPath)
+ {
+ uint winRt = 0;
+ try
+ {
+ winRt = await PdfWinRtHelper.GetPageCountAsync(pdfPath).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PDF WinRT 获取页数失败,将尝试 PDFtoImage(MIT): {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ if (winRt > 0)
+ return winRt;
+
+ try
+ {
+ return await Task.Run(() => PdfToImageMitHelper.GetPageCount(pdfPath)).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PDFtoImage(MIT) 获取页数失败: {ex.Message}", LogHelper.LogType.Warning);
+ return 0;
+ }
+ }
+
+ public static async Task RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
+ {
+ try
+ {
+ BitmapSource win = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(pdfPath, pageIndex).ConfigureAwait(false);
+ if (win != null)
+ return win;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PDF WinRT 渲染失败,将尝试 PDFtoImage(MIT): {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ try
+ {
+ return await PdfToImageMitHelper.RenderPageToBitmapSourceAsync(pdfPath, pageIndex).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PDFtoImage(MIT) 渲染失败: {ex.Message}", LogHelper.LogType.Warning);
+ return null;
+ }
+ }
+ }
+}
diff --git a/Ink Canvas/Helpers/PdfToImageMitHelper.cs b/Ink Canvas/Helpers/PdfToImageMitHelper.cs
new file mode 100644
index 00000000..970e103a
--- /dev/null
+++ b/Ink Canvas/Helpers/PdfToImageMitHelper.cs
@@ -0,0 +1,96 @@
+using PDFtoImage;
+using SkiaSharp;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media.Imaging;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 使用 NuGet「PDFtoImage」(MIT,基于 PDFium/SkiaSharp) 解析/渲染 PDF,作为 WinRT 不可用时的备用实现。
+ ///
+ internal static class PdfToImageMitHelper
+ {
+ public static uint GetPageCount(string pdfPath)
+ {
+ if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
+ return 0;
+
+ try
+ {
+ using (var fs = new FileStream(pdfPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ int n = Conversion.GetPageCount(fs, leaveOpen: true, password: null);
+ return n < 0 ? 0u : (uint)n;
+ }
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// 在工作线程加载 PDF 页为 ,在 UI 线程编码为 WPF 。
+ ///
+ public static async Task RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
+ {
+ if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
+ return null;
+
+ int page = checked((int)pageIndex);
+
+ SKBitmap skBitmap = await Task.Run(() =>
+ {
+ try
+ {
+ using (var fs = new FileStream(pdfPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ return Conversion.ToImage(fs, System.Index.FromStart(page), leaveOpen: true, password: null, options: default);
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }).ConfigureAwait(false);
+
+ if (skBitmap == null)
+ return null;
+
+ try
+ {
+ if (Application.Current?.Dispatcher == null)
+ return null;
+
+ return await Application.Current.Dispatcher.InvokeAsync(() => EncodeSkBitmapToBitmapSource(skBitmap));
+ }
+ finally
+ {
+ skBitmap.Dispose();
+ }
+ }
+
+ private static BitmapSource EncodeSkBitmapToBitmapSource(SKBitmap bitmap)
+ {
+ using (var image = SKImage.FromBitmap(bitmap))
+ using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
+ {
+ var ms = new MemoryStream();
+ data.SaveTo(ms);
+ ms.Position = 0;
+
+ var bi = new BitmapImage();
+ bi.BeginInit();
+ bi.StreamSource = ms;
+ bi.CacheOption = BitmapCacheOption.OnLoad;
+ bi.EndInit();
+ bi.Freeze();
+ ms.Dispose();
+ return bi;
+ }
+ }
+ }
+}
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 44764346..2607a500 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -141,6 +141,7 @@
+
diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
index e91bcb73..d8856924 100644
--- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
@@ -1007,7 +1007,7 @@ namespace Ink_Canvas
string newFilePath = Path.Combine(savePath, timestamp + ".pdf");
await Task.Run(() => File.Copy(filePath, newFilePath, true));
- uint pageCount = await PdfWinRtHelper.GetPageCountAsync(newFilePath);
+ uint pageCount = await PdfDocumentRenderHelper.GetPageCountAsync(newFilePath);
if (pageCount == 0)
{
ShowNotification("无法打开 PDF(可能已加密、损坏或不支持)。");
@@ -1037,7 +1037,7 @@ namespace Ink_Canvas
try
{
- uint pageCount = await PdfWinRtHelper.GetPageCountAsync(info.SourcePath);
+ uint pageCount = await PdfDocumentRenderHelper.GetPageCountAsync(info.SourcePath);
if (pageCount == 0) return;
bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded;
diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json
index d96e90d0..ed98aa02 100644
--- a/Ink Canvas/packages.lock.json
+++ b/Ink Canvas/packages.lock.json
@@ -157,6 +157,21 @@
"Microsoft.Win32.Registry": "5.0.0"
}
},
+ "PDFtoImage": {
+ "type": "Direct",
+ "requested": "[5.1.0, )",
+ "resolved": "5.1.0",
+ "contentHash": "QbGA2pdMCdQFI0MpXXDD7teGJsOR3ic9T8iGv3hXTtyMSjJkVgFDKRxRU9Ri31DbkkpvY7azzNeUTWdf5AqPZg==",
+ "dependencies": {
+ "SkiaSharp": "3.119.0",
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": "3.119.0",
+ "SkiaSharp.NativeAssets.Win32": "3.119.0",
+ "SkiaSharp.NativeAssets.macOS": "3.119.0",
+ "bblanchon.PDFium.Linux": "137.0.7149",
+ "bblanchon.PDFium.Win32": "137.0.7149",
+ "bblanchon.PDFium.macOS": "137.0.7149"
+ }
+ },
"Sentry": {
"type": "Direct",
"requested": "[6.2.0, )",
@@ -190,6 +205,21 @@
"resolved": "6.3.0.90",
"contentHash": "WVTb5MxwGqKdeasd3nG5udlV4t6OpvkFanziwI133K0/QJ5FvZmfzRQgpAjGTJhQfIA8GP7AzKQ3sTY9JOFk8Q=="
},
+ "bblanchon.PDFium.Linux": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "tT2GeyKE39ci3THZ/ddzhvZ3efgfCdEsZtDfEH+kDV1dfl2hxFeZesdTcRnxj3hp/ScnxTNJNa0RZtwOwc0I3g=="
+ },
+ "bblanchon.PDFium.macOS": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "OXZJhlJ/jynL5dz9/OJ65UFCIqZBepdyd84xE11AAWgG6UPERi2p4tEmmIAbloZ+aRBtaCRWNk5pgyF2WDe7vQ=="
+ },
+ "bblanchon.PDFium.Win32": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "M3C2UCvKD1X6hJ/VB6arBMhI91OYHzybWMAOVFUKuEpfTR1nKfbYa/1dNVHKPmc8ST0SAYeVOlkv+XlJrsXy/Q=="
+ },
"Fody": {
"type": "Transitive",
"resolved": "6.8.2",
@@ -240,6 +270,31 @@
"resolved": "4.0.0",
"contentHash": "5HKzttVKWeKoDQKJd3+J7Dy1MW6gbNNYfftkVufe2ddFQD0kXjnT1IN3ZJBfF6QVEQmHpQSp+/PT7Jo2YyHFcw=="
},
+ "SkiaSharp": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "gR9yVoOta2Mc1Rxt15LD65AckfHMfwjIs/3kkD59C9bT2nYYISsE6uz3t4aMPNHA6CgsIL0Ssn+jE5OVilZ1yw==",
+ "dependencies": {
+ "SkiaSharp.NativeAssets.Win32": "3.119.0",
+ "SkiaSharp.NativeAssets.macOS": "3.119.0",
+ "System.Memory": "4.5.5"
+ }
+ },
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "e92vdqf1VOETPjy1T67Fs1zPxfGMM1nbrpt69GM5foXSI/iIbq6L9avPz/bl/DbWtb81D0yF/NKjRmXuOZoLcg=="
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "YE1vNn0Nyw2PWtv7hw1PYkKJO0itFiQp9vSqGppZUKzQJqwp28a2jgdCMPfYtOiR8KCnDgZqQoynqJRRaE2ZVg=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
+ },
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
@@ -351,6 +406,21 @@
}
},
".NETFramework,Version=v4.7.2/win": {
+ "bblanchon.PDFium.Linux": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "tT2GeyKE39ci3THZ/ddzhvZ3efgfCdEsZtDfEH+kDV1dfl2hxFeZesdTcRnxj3hp/ScnxTNJNa0RZtwOwc0I3g=="
+ },
+ "bblanchon.PDFium.macOS": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "OXZJhlJ/jynL5dz9/OJ65UFCIqZBepdyd84xE11AAWgG6UPERi2p4tEmmIAbloZ+aRBtaCRWNk5pgyF2WDe7vQ=="
+ },
+ "bblanchon.PDFium.Win32": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "M3C2UCvKD1X6hJ/VB6arBMhI91OYHzybWMAOVFUKuEpfTR1nKfbYa/1dNVHKPmc8ST0SAYeVOlkv+XlJrsXy/Q=="
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -360,6 +430,21 @@
"System.Security.Principal.Windows": "5.0.0"
}
},
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "e92vdqf1VOETPjy1T67Fs1zPxfGMM1nbrpt69GM5foXSI/iIbq6L9avPz/bl/DbWtb81D0yF/NKjRmXuOZoLcg=="
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "YE1vNn0Nyw2PWtv7hw1PYkKJO0itFiQp9vSqGppZUKzQJqwp28a2jgdCMPfYtOiR8KCnDgZqQoynqJRRaE2ZVg=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
+ },
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -380,6 +465,21 @@
}
},
".NETFramework,Version=v4.7.2/win-arm64": {
+ "bblanchon.PDFium.Linux": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "tT2GeyKE39ci3THZ/ddzhvZ3efgfCdEsZtDfEH+kDV1dfl2hxFeZesdTcRnxj3hp/ScnxTNJNa0RZtwOwc0I3g=="
+ },
+ "bblanchon.PDFium.macOS": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "OXZJhlJ/jynL5dz9/OJ65UFCIqZBepdyd84xE11AAWgG6UPERi2p4tEmmIAbloZ+aRBtaCRWNk5pgyF2WDe7vQ=="
+ },
+ "bblanchon.PDFium.Win32": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "M3C2UCvKD1X6hJ/VB6arBMhI91OYHzybWMAOVFUKuEpfTR1nKfbYa/1dNVHKPmc8ST0SAYeVOlkv+XlJrsXy/Q=="
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -389,6 +489,21 @@
"System.Security.Principal.Windows": "5.0.0"
}
},
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "e92vdqf1VOETPjy1T67Fs1zPxfGMM1nbrpt69GM5foXSI/iIbq6L9avPz/bl/DbWtb81D0yF/NKjRmXuOZoLcg=="
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "YE1vNn0Nyw2PWtv7hw1PYkKJO0itFiQp9vSqGppZUKzQJqwp28a2jgdCMPfYtOiR8KCnDgZqQoynqJRRaE2ZVg=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
+ },
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -409,6 +524,21 @@
}
},
".NETFramework,Version=v4.7.2/win-x64": {
+ "bblanchon.PDFium.Linux": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "tT2GeyKE39ci3THZ/ddzhvZ3efgfCdEsZtDfEH+kDV1dfl2hxFeZesdTcRnxj3hp/ScnxTNJNa0RZtwOwc0I3g=="
+ },
+ "bblanchon.PDFium.macOS": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "OXZJhlJ/jynL5dz9/OJ65UFCIqZBepdyd84xE11AAWgG6UPERi2p4tEmmIAbloZ+aRBtaCRWNk5pgyF2WDe7vQ=="
+ },
+ "bblanchon.PDFium.Win32": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "M3C2UCvKD1X6hJ/VB6arBMhI91OYHzybWMAOVFUKuEpfTR1nKfbYa/1dNVHKPmc8ST0SAYeVOlkv+XlJrsXy/Q=="
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -418,6 +548,21 @@
"System.Security.Principal.Windows": "5.0.0"
}
},
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "e92vdqf1VOETPjy1T67Fs1zPxfGMM1nbrpt69GM5foXSI/iIbq6L9avPz/bl/DbWtb81D0yF/NKjRmXuOZoLcg=="
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "YE1vNn0Nyw2PWtv7hw1PYkKJO0itFiQp9vSqGppZUKzQJqwp28a2jgdCMPfYtOiR8KCnDgZqQoynqJRRaE2ZVg=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
+ },
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -438,6 +583,21 @@
}
},
".NETFramework,Version=v4.7.2/win-x86": {
+ "bblanchon.PDFium.Linux": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "tT2GeyKE39ci3THZ/ddzhvZ3efgfCdEsZtDfEH+kDV1dfl2hxFeZesdTcRnxj3hp/ScnxTNJNa0RZtwOwc0I3g=="
+ },
+ "bblanchon.PDFium.macOS": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "OXZJhlJ/jynL5dz9/OJ65UFCIqZBepdyd84xE11AAWgG6UPERi2p4tEmmIAbloZ+aRBtaCRWNk5pgyF2WDe7vQ=="
+ },
+ "bblanchon.PDFium.Win32": {
+ "type": "Transitive",
+ "resolved": "137.0.7149",
+ "contentHash": "M3C2UCvKD1X6hJ/VB6arBMhI91OYHzybWMAOVFUKuEpfTR1nKfbYa/1dNVHKPmc8ST0SAYeVOlkv+XlJrsXy/Q=="
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -447,6 +607,21 @@
"System.Security.Principal.Windows": "5.0.0"
}
},
+ "SkiaSharp.NativeAssets.Linux.NoDependencies": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "e92vdqf1VOETPjy1T67Fs1zPxfGMM1nbrpt69GM5foXSI/iIbq6L9avPz/bl/DbWtb81D0yF/NKjRmXuOZoLcg=="
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "YE1vNn0Nyw2PWtv7hw1PYkKJO0itFiQp9vSqGppZUKzQJqwp28a2jgdCMPfYtOiR8KCnDgZqQoynqJRRaE2ZVg=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "3.119.0",
+ "contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
+ },
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",