From c77beb662e163a2bbf0c5bad2d916288f440279e Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Sun, 5 Apr 2026 17:31:35 +0800
Subject: [PATCH] =?UTF-8?q?add: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 | 136 ++++++++
Ink Canvas/Helpers/PdfWinRtHelper.cs | 80 +++++
Ink Canvas/InkCanvasForClass.csproj | 1 +
Ink Canvas/MainWindow.xaml | 103 ++++++
.../MainWindow_cs/MW_ElementsControls.cs | 297 +++++++++++++++---
.../MainWindow_cs/MW_FloatingBarIcons.cs | 178 +++++------
.../MainWindow_cs/MW_SelectionGestures.cs | 5 +-
Ink Canvas/Windows/PPTQuickPanel.xaml.cs | 79 ++---
Ink Canvas/packages.lock.json | 29 +-
9 files changed, 701 insertions(+), 207 deletions(-)
create mode 100644 Ink Canvas/Controls/PdfEmbeddedView.cs
create mode 100644 Ink Canvas/Helpers/PdfWinRtHelper.cs
diff --git a/Ink Canvas/Controls/PdfEmbeddedView.cs b/Ink Canvas/Controls/PdfEmbeddedView.cs
new file mode 100644
index 00000000..e5ea8bc4
--- /dev/null
+++ b/Ink Canvas/Controls/PdfEmbeddedView.cs
@@ -0,0 +1,136 @@
+using Ink_Canvas.Helpers;
+using System;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace Ink_Canvas.Controls
+{
+ ///
+ /// 画布上的多页 PDF:仅显示当前页;翻页与页码由主窗口 PDF 侧栏控制(无 XAML 文件)。
+ ///
+ public class PdfEmbeddedView : UserControl
+ {
+ private readonly Image _pageImage;
+
+ private string _pdfPath;
+ private uint _pageCount;
+ private uint _currentIndex;
+ private bool _compressLargePictures;
+ private bool _isPagingBusy;
+ private bool _layoutSizeLocked;
+
+ /// 页码或可翻页状态变化(用于更新侧栏)。
+ public event EventHandler PageNavigationStateChanged;
+
+ public PdfEmbeddedView()
+ {
+ MinWidth = 80;
+ MinHeight = 60;
+
+ var grid = new Grid { ClipToBounds = true };
+ _pageImage = new Image
+ {
+ Stretch = Stretch.Uniform,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ grid.Children.Add(_pageImage);
+ Content = grid;
+ }
+
+ ///
+ /// 初始化并显示第一页;由 MainWindow 在 UI 线程创建后调用。
+ ///
+ public async Task InitializeAsync(string pdfFilePath, uint pageCount, bool compressLargePictures)
+ {
+ _pdfPath = pdfFilePath ?? throw new ArgumentNullException(nameof(pdfFilePath));
+ _pageCount = pageCount;
+ _compressLargePictures = compressLargePictures;
+ _currentIndex = 0;
+
+ await ShowPageAsync(_currentIndex);
+ }
+
+ public string PdfPath => _pdfPath;
+
+ public uint PageCount => _pageCount;
+
+ public uint CurrentPageIndex => _currentIndex;
+
+ public string PageLabelText => _pageCount == 0 ? "" : $"{_currentIndex + 1} / {_pageCount}";
+
+ public bool CanGoPrevious => !_isPagingBusy && _pageCount > 1 && _currentIndex > 0;
+
+ public bool CanGoNext => !_isPagingBusy && _pageCount > 1 && _currentIndex < _pageCount - 1;
+
+ public async Task GoToPreviousPageAsync()
+ {
+ await GoRelativeAsync(-1);
+ }
+
+ public async Task GoToNextPageAsync()
+ {
+ await GoRelativeAsync(1);
+ }
+
+ private void NotifyPageNavigationStateChanged()
+ {
+ PageNavigationStateChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ private async Task GoRelativeAsync(int delta)
+ {
+ if (_isPagingBusy || _pageCount <= 1)
+ return;
+ int next = (int)_currentIndex + delta;
+ if (next < 0 || next >= _pageCount)
+ return;
+ _currentIndex = (uint)next;
+ await ShowPageAsync(_currentIndex);
+ }
+
+ private async Task ShowPageAsync(uint pageIndex)
+ {
+ _isPagingBusy = true;
+ NotifyPageNavigationStateChanged();
+ try
+ {
+ BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
+ if (raw == null)
+ return;
+
+ BitmapSource display = ApplyCompressionIfNeeded(raw);
+ _pageImage.Source = display;
+ if (!_layoutSizeLocked)
+ {
+ Width = display.PixelWidth;
+ Height = display.PixelHeight;
+ _layoutSizeLocked = true;
+ }
+ }
+ finally
+ {
+ _isPagingBusy = false;
+ NotifyPageNavigationStateChanged();
+ }
+ }
+
+ private BitmapSource ApplyCompressionIfNeeded(BitmapSource rendered)
+ {
+ int width = rendered.PixelWidth;
+ int height = rendered.PixelHeight;
+ if (_compressLargePictures && (width > 1920 || height > 1080))
+ {
+ double scaleX = 1920.0 / width;
+ double scaleY = 1080.0 / height;
+ double scale = Math.Min(scaleX, scaleY);
+ return new TransformedBitmap(rendered, new ScaleTransform(scale, scale));
+ }
+
+ return rendered;
+ }
+ }
+}
diff --git a/Ink Canvas/Helpers/PdfWinRtHelper.cs b/Ink Canvas/Helpers/PdfWinRtHelper.cs
new file mode 100644
index 00000000..578b6931
--- /dev/null
+++ b/Ink Canvas/Helpers/PdfWinRtHelper.cs
@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media.Imaging;
+using Windows.Data.Pdf;
+using Windows.Storage;
+using Windows.Storage.Streams;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 使用 Windows.Data.Pdf(WinRT)将 PDF 页渲染为 WPF 可用的位图。
+ ///
+ internal static class PdfWinRtHelper
+ {
+ public static async Task GetPageCountAsync(string pdfPath)
+ {
+ if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
+ return 0;
+
+ var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
+ var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
+ if (doc.IsPasswordProtected)
+ return 0;
+ return doc.PageCount;
+ }
+
+ public static async Task RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
+ {
+ if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
+ return null;
+
+ var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
+ var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
+ if (doc.IsPasswordProtected)
+ return null;
+ if (pageIndex >= doc.PageCount)
+ return null;
+
+ var page = doc.GetPage(pageIndex);
+ try
+ {
+ using (var ras = new InMemoryRandomAccessStream())
+ {
+ await page.RenderToStreamAsync(ras).AsTask();
+ ras.Seek(0);
+
+ var ms = new MemoryStream();
+ using (var netStream = ras.AsStreamForRead())
+ netStream.CopyTo(ms);
+ ms.Position = 0;
+
+ try
+ {
+ return await Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ var bi = new BitmapImage();
+ bi.BeginInit();
+ bi.StreamSource = ms;
+ bi.CacheOption = BitmapCacheOption.OnLoad;
+ bi.EndInit();
+ bi.Freeze();
+ return (BitmapSource)bi;
+ });
+ }
+ finally
+ {
+ ms.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ (page as IDisposable)?.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 969d8176..44764346 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -141,6 +141,7 @@
+
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index 070af88d..8309ac2e 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -4948,6 +4948,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+