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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + element.Loaded += (s, args) => { Dispatcher.BeginInvoke(new Action(() => { // 初始化TransformGroup - InitializeElementTransform(image); + InitializeElementTransform(element); // 居中缩放 - CenterAndScaleElement(image); + CenterAndScaleElement(element); // 最后绑定事件处理器 - BindElementEvents(image); + BindElementEvents(element); - LogHelper.WriteLogToFile($"图片插入完成: {image.Name}"); + LogHelper.WriteLogToFile($"图片插入完成: {element.Name}"); }), DispatcherPriority.Loaded); }; - timeMachine.CommitElementInsertHistory(image); + timeMachine.CommitElementInsertHistory(element); // 插入图片后切换到选择模式并刷新浮动栏高光显示 SetCurrentToolMode(InkCanvasEditingMode.Select); @@ -270,13 +271,13 @@ namespace Ink_Canvas ApplyMouseDragTransform(element, currentPoint, dragStartPoint); // 如果是图片元素,更新工具栏位置 - if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(element); } // 如果是图片元素,更新选择点位置 - if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) { UpdateImageResizeHandlesPosition(GetElementActualBounds(element)); } @@ -306,13 +307,13 @@ namespace Ink_Canvas ApplyWheelScaleTransform(element, e); // 如果是图片元素,更新工具栏位置 - if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(element); } // 如果是图片元素,更新选择点位置 - if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) { UpdateImageResizeHandlesPosition(GetElementActualBounds(element)); } @@ -396,13 +397,13 @@ namespace Ink_Canvas ApplyTouchManipulationTransform(element, e); // 如果是图片元素,更新工具栏位置 - if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(element); } // 如果是图片元素,更新选择点位置 - if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) { UpdateImageResizeHandlesPosition(GetElementActualBounds(element)); } @@ -523,12 +524,15 @@ namespace Ink_Canvas currentSelectedElement = element; // 根据元素类型显示不同的选择工具栏 - if (element is Image) + if (IsBitmapLikeCanvasElement(element)) { + if (BorderPdfPageSidebar != null) + BorderPdfPageSidebar.Visibility = element is PdfEmbeddedView ? Visibility.Visible : Visibility.Collapsed; + // 显示图片选择工具栏并设置位置 if (BorderImageSelectionControl != null) { - // 计算工具栏位置 + // 计算工具栏位置(内部会同步 PDF 右侧栏位置) UpdateImageSelectionToolbarPosition(element); BorderImageSelectionControl.Visibility = Visibility.Visible; } @@ -536,11 +540,25 @@ namespace Ink_Canvas // 显示图片缩放选择点 ShowImageResizeHandles(element); + if (element is PdfEmbeddedView pdfView) + { + pdfView.PageNavigationStateChanged -= SelectedPdf_PageNavigationStateChanged; + pdfView.PageNavigationStateChanged += SelectedPdf_PageNavigationStateChanged; + UpdatePdfSidebarFromSelectedPdf(); + UpdatePdfPageSidebarPosition(pdfView); + } + else + ResetPdfSidebarToIdle(); + // 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制 // 不需要直接设置BorderStrokeSelectionControl.Visibility } else { + if (BorderPdfPageSidebar != null) + BorderPdfPageSidebar.Visibility = Visibility.Collapsed; + ResetPdfSidebarToIdle(); + // 隐藏图片选择工具栏 if (BorderImageSelectionControl != null) { @@ -584,6 +602,15 @@ namespace Ink_Canvas { // 去除选中效果 + if (element is PdfEmbeddedView pdfView) + { + pdfView.PageNavigationStateChanged -= SelectedPdf_PageNavigationStateChanged; + } + + if (BorderPdfPageSidebar != null) + BorderPdfPageSidebar.Visibility = Visibility.Collapsed; + ResetPdfSidebarToIdle(); + // 隐藏图片选择工具栏 if (BorderImageSelectionControl != null) { @@ -914,15 +941,24 @@ namespace Ink_Canvas /// - 否则使用原始尺寸 /// - 返回创建的Image对象 /// - private async Task CreateAndCompressImageAsync(string filePath) + /// 与图片选择工具栏、缩放控制点联动的画布位图类元素(普通图片或多页 PDF 嵌入)。 + private static bool IsBitmapLikeCanvasElement(FrameworkElement fe) { + return fe is Image || fe is PdfEmbeddedView; + } + + private async Task CreateAndCompressImageAsync(string filePath) + { + string fileExtension = Path.GetExtension(filePath); + if (string.Equals(fileExtension, ".pdf", StringComparison.OrdinalIgnoreCase)) + return await CreateAndCompressImageFromPdfAsync(filePath); + string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency"); if (!Directory.Exists(savePath)) { Directory.CreateDirectory(savePath); } - string fileExtension = Path.GetExtension(filePath); string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); string newFilePath = Path.Combine(savePath, timestamp + fileExtension); @@ -965,6 +1001,43 @@ namespace Ink_Canvas return image; }); } + + /// + /// 插入完整 PDF:嵌入控件内可翻页,右下角显示页码(类似希沃白板交互)。 + /// + private async Task CreateAndCompressImageFromPdfAsync(string filePath) + { + try + { + string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency"); + if (!Directory.Exists(savePath)) + Directory.CreateDirectory(savePath); + + string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); + string newFilePath = Path.Combine(savePath, timestamp + ".pdf"); + await Task.Run(() => File.Copy(filePath, newFilePath, true)); + + uint pageCount = await PdfWinRtHelper.GetPageCountAsync(newFilePath); + if (pageCount == 0) + { + ShowNotification("无法打开 PDF(可能已加密、损坏或不支持)。"); + return null; + } + + bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded; + var view = new PdfEmbeddedView(); + await view.InitializeAsync(newFilePath, pageCount, compress); + view.Tag = filePath; + return view; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"插入 PDF 失败: {ex.Message}", LogHelper.LogType.Error); + ShowNotification($"插入 PDF 失败: {ex.Message}"); + return null; + } + } + #endregion #region Media @@ -1453,6 +1526,9 @@ namespace Ink_Canvas // 设置工具栏位置 BorderImageSelectionControl.Margin = new Thickness(toolbarLeft, toolbarTop, 0, 0); + + if (element is PdfEmbeddedView && BorderPdfPageSidebar?.Visibility == Visibility.Visible) + UpdatePdfPageSidebarPosition(element); } catch (Exception ex) { @@ -1460,6 +1536,54 @@ namespace Ink_Canvas } } + private const double PdfPageSidebarGap = 10; + + /// + /// 将 PDF 专用页码栏贴在当前所选 PDF 的右侧(画布坐标,与底部选中栏一致)。 + /// + private void UpdatePdfPageSidebarPosition(FrameworkElement element) + { + try + { + if (BorderPdfPageSidebar == null || inkCanvas == null || !(element is PdfEmbeddedView)) + return; + + Rect b = GetElementActualBounds(element); + + BorderPdfPageSidebar.Measure(new Size(BorderPdfPageSidebar.Width, double.PositiveInfinity)); + double sidebarW = BorderPdfPageSidebar.DesiredSize.Width; + double sidebarH = BorderPdfPageSidebar.DesiredSize.Height; + if (sidebarW <= 0) + sidebarW = BorderPdfPageSidebar.Width; + if (sidebarH <= 0) + sidebarH = BorderPdfPageSidebar.ActualHeight; + if (sidebarH <= 0) + sidebarH = 220; + + double left = b.Right + PdfPageSidebarGap; + double top = b.Top + (b.Height * 0.5) - (sidebarH * 0.5); + + double maxLeft = Math.Max(0, inkCanvas.ActualWidth - sidebarW); + double maxTop = Math.Max(0, inkCanvas.ActualHeight - sidebarH); + + if (left > maxLeft) + { + double leftAlt = b.Left - PdfPageSidebarGap - sidebarW; + if (leftAlt >= 0) + left = leftAlt; + } + + left = Math.Max(0, Math.Min(left, maxLeft)); + top = Math.Max(0, Math.Min(top, maxTop)); + + BorderPdfPageSidebar.Margin = new Thickness(left, top, 0, 0); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新 PDF 右侧页码栏位置失败: {ex.Message}", LogHelper.LogType.Error); + } + } + /// /// 获取元素的实际边界(考虑变换) /// @@ -1644,7 +1768,7 @@ namespace Ink_Canvas ApplyRotateTransform(currentSelectedElement, -45); // 更新工具栏位置 - if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(currentSelectedElement); } @@ -1678,7 +1802,7 @@ namespace Ink_Canvas ApplyRotateTransform(currentSelectedElement, 45); // 更新工具栏位置 - if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(currentSelectedElement); } @@ -1714,7 +1838,7 @@ namespace Ink_Canvas ApplyScaleTransform(currentSelectedElement, 0.9, elementCenter); // 更新工具栏位置 - if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(currentSelectedElement); } @@ -1750,7 +1874,7 @@ namespace Ink_Canvas ApplyScaleTransform(currentSelectedElement, 1.1, elementCenter); // 更新工具栏位置 - if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible) + if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible) { UpdateImageSelectionToolbarPosition(currentSelectedElement); } @@ -1764,19 +1888,96 @@ namespace Ink_Canvas } } + private void ResetPdfSidebarToIdle() + { + if (TextBlockPdfSidebarPageLabel != null) + { + TextBlockPdfSidebarPageLabel.Text = "— / —"; + TextBlockPdfSidebarPageLabel.Opacity = 0.55; + } + + if (BorderPdfSidebarPagePrev != null) + { + BorderPdfSidebarPagePrev.Opacity = 0.35; + BorderPdfSidebarPagePrev.IsHitTestVisible = false; + } + + if (BorderPdfSidebarPageNext != null) + { + BorderPdfSidebarPageNext.Opacity = 0.35; + BorderPdfSidebarPageNext.IsHitTestVisible = false; + } + } + + private void UpdatePdfSidebarFromSelectedPdf() + { + if (currentSelectedElement is PdfEmbeddedView pdf) + { + if (TextBlockPdfSidebarPageLabel != null) + { + TextBlockPdfSidebarPageLabel.Text = pdf.PageLabelText; + TextBlockPdfSidebarPageLabel.Opacity = 1.0; + } + + bool prevOk = pdf.CanGoPrevious; + bool nextOk = pdf.CanGoNext; + if (BorderPdfSidebarPagePrev != null) + { + BorderPdfSidebarPagePrev.Opacity = prevOk ? 1.0 : 0.35; + BorderPdfSidebarPagePrev.IsHitTestVisible = prevOk; + } + + if (BorderPdfSidebarPageNext != null) + { + BorderPdfSidebarPageNext.Opacity = nextOk ? 1.0 : 0.35; + BorderPdfSidebarPageNext.IsHitTestVisible = nextOk; + } + } + } + + private void SelectedPdf_PageNavigationStateChanged(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(new Action(() => + { + UpdatePdfSidebarFromSelectedPdf(); + if (currentSelectedElement != null && IsBitmapLikeCanvasElement(currentSelectedElement)) + { + UpdateImageSelectionToolbarPosition(currentSelectedElement); + if (ImageResizeHandlesCanvas?.Visibility == Visibility.Visible) + UpdateImageResizeHandlesPosition(GetElementActualBounds(currentSelectedElement)); + } + }), DispatcherPriority.Background); + } + + private async void BorderPdfSidebarPagePrev_MouseUp(object sender, MouseButtonEventArgs e) + { + try + { + if (currentSelectedElement is PdfEmbeddedView pdf && pdf.CanGoPrevious) + await pdf.GoToPreviousPageAsync(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PDF 上一页失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + private async void BorderPdfSidebarPageNext_MouseUp(object sender, MouseButtonEventArgs e) + { + try + { + if (currentSelectedElement is PdfEmbeddedView pdf && pdf.CanGoNext) + await pdf.GoToNextPageAsync(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PDF 下一页失败: {ex.Message}", LogHelper.LogType.Error); + } + } + /// /// 处理图片删除功能 /// - /// 事件发送者 - /// 事件参数 - /// - /// - 检查当前是否有选中元素 - /// - 保存删除前的编辑模式 - /// - 记录删除历史 - /// - 从画布中移除 - /// - 清除选中状态 - /// - 包含异常处理 - /// private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e) { try @@ -1789,11 +1990,12 @@ namespace Ink_Canvas // 记录删除历史 timeMachine.CommitElementRemoveHistory(currentSelectedElement); + var toRemove = currentSelectedElement; // 从画布中移除 - inkCanvas.Children.Remove(currentSelectedElement); + inkCanvas.Children.Remove(toRemove); // 清除选中状态 - UnselectElement(currentSelectedElement); + UnselectElement(toRemove); currentSelectedElement = null; // 恢复到删除前的编辑模式 @@ -2029,7 +2231,7 @@ namespace Ink_Canvas { try { - if (currentSelectedElement is Image image && sender is Ellipse ellipse) + if (IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse) { isResizingImage = true; imageResizeStartPoint = e.GetPosition(inkCanvas); @@ -2072,10 +2274,10 @@ namespace Ink_Canvas { try { - if (isResizingImage && currentSelectedElement is Image image && sender is Ellipse ellipse) + if (isResizingImage && IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse) { var currentPoint = e.GetPosition(inkCanvas); - ResizeImageByHandle(image, imageResizeStartPoint, currentPoint, activeResizeHandle); + ResizeImageByHandle(currentSelectedElement, imageResizeStartPoint, currentPoint, activeResizeHandle); imageResizeStartPoint = currentPoint; e.Handled = true; } @@ -2087,11 +2289,11 @@ namespace Ink_Canvas } // 根据控制点缩放图片 - private void ResizeImageByHandle(Image image, Point startPoint, Point currentPoint, string handleName) + private void ResizeImageByHandle(FrameworkElement element, Point startPoint, Point currentPoint, string handleName) { try { - if (image.RenderTransform is TransformGroup transformGroup) + if (element.RenderTransform is TransformGroup transformGroup) { var scaleTransform = transformGroup.Children.OfType().FirstOrDefault(); var translateTransform = transformGroup.Children.OfType().FirstOrDefault(); @@ -2099,7 +2301,7 @@ namespace Ink_Canvas if (scaleTransform == null || translateTransform == null) return; // 获取图片的当前边界 - Rect currentBounds = GetElementActualBounds(image); + Rect currentBounds = GetElementActualBounds(element); double deltaX = currentPoint.X - startPoint.X; double deltaY = currentPoint.Y - startPoint.Y; @@ -2160,7 +2362,10 @@ namespace Ink_Canvas translateTransform.Y += translateY; // 更新选择点位置 - UpdateImageResizeHandlesPosition(GetElementActualBounds(image)); + UpdateImageResizeHandlesPosition(GetElementActualBounds(element)); + + if (BorderImageSelectionControl?.Visibility == Visibility.Visible) + UpdateImageSelectionToolbarPosition(element); } } catch (Exception ex) diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index b761d751..310d2ebc 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -1,3 +1,4 @@ +using Ink_Canvas.Controls; using Ink_Canvas.Helpers; using iNKORE.UI.WPF.Modern; using System; @@ -3774,32 +3775,29 @@ namespace Ink_Canvas // Open file dialog to select image var dialog = new OpenFileDialog { - Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif" + Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf" }; if (dialog.ShowDialog() == true) { string filePath = dialog.FileName; - Image image = await CreateAndCompressImageAsync(filePath); - if (image != null) + FrameworkElement element = await CreateAndCompressImageAsync(filePath); + if (element != null) { string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; + element.Name = timestamp; // 初始化TransformGroup - if (image is FrameworkElement element) - { - var transformGroup = new TransformGroup(); - transformGroup.Children.Add(new ScaleTransform(1, 1)); - transformGroup.Children.Add(new TranslateTransform(0, 0)); - transformGroup.Children.Add(new RotateTransform(0)); - element.RenderTransform = transformGroup; - } + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(1, 1)); + transformGroup.Children.Add(new TranslateTransform(0, 0)); + transformGroup.Children.Add(new RotateTransform(0)); + element.RenderTransform = transformGroup; - CenterAndScaleElement(image); + CenterAndScaleElement(element); // 设置图片属性,避免被InkCanvas选择系统处理 - image.IsHitTestVisible = true; - image.Focusable = false; + element.IsHitTestVisible = true; + element.Focusable = false; // 初始化InkCanvas选择设置 if (inkCanvas != null) @@ -3810,29 +3808,25 @@ namespace Ink_Canvas inkCanvas.EditingMode = InkCanvasEditingMode.None; } - inkCanvas.Children.Add(image); + inkCanvas.Children.Add(element); // 绑定事件处理器 - if (image is FrameworkElement elementForEvents) - { - // 鼠标事件 - elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown; - elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp; - elementForEvents.MouseMove += Element_MouseMove; - elementForEvents.MouseWheel += Element_MouseWheel; + element.MouseLeftButtonDown += Element_MouseLeftButtonDown; + element.MouseLeftButtonUp += Element_MouseLeftButtonUp; + element.MouseMove += Element_MouseMove; + element.MouseWheel += Element_MouseWheel; - // 触摸事件 - elementForEvents.TouchDown += Element_TouchDown; - elementForEvents.TouchUp += Element_TouchUp; - elementForEvents.IsManipulationEnabled = true; - elementForEvents.ManipulationDelta += Element_ManipulationDelta; - elementForEvents.ManipulationCompleted += Element_ManipulationCompleted; + // 触摸事件 + element.TouchDown += Element_TouchDown; + element.TouchUp += Element_TouchUp; + element.IsManipulationEnabled = true; + element.ManipulationDelta += Element_ManipulationDelta; + element.ManipulationCompleted += Element_ManipulationCompleted; - // 设置光标 - elementForEvents.Cursor = Cursors.Hand; - } + // 设置光标 + element.Cursor = Cursors.Hand; - timeMachine.CommitElementInsertHistory(image); + timeMachine.CommitElementInsertHistory(element); // 插入图片后切换到选择模式并刷新浮动栏高光显示 SetCurrentToolMode(InkCanvasEditingMode.Select); @@ -3847,32 +3841,29 @@ namespace Ink_Canvas { var dialog = new OpenFileDialog { - Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif" + Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf" }; if (dialog.ShowDialog() == true) { string filePath = dialog.FileName; - Image image = await CreateAndCompressImageAsync(filePath); - if (image != null) + FrameworkElement element = await CreateAndCompressImageAsync(filePath); + if (element != null) { string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; + element.Name = timestamp; // 初始化TransformGroup - if (image is FrameworkElement element) - { - var transformGroup = new TransformGroup(); - transformGroup.Children.Add(new ScaleTransform(1, 1)); - transformGroup.Children.Add(new TranslateTransform(0, 0)); - transformGroup.Children.Add(new RotateTransform(0)); - element.RenderTransform = transformGroup; - } + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(1, 1)); + transformGroup.Children.Add(new TranslateTransform(0, 0)); + transformGroup.Children.Add(new RotateTransform(0)); + element.RenderTransform = transformGroup; - CenterAndScaleElement(image); + CenterAndScaleElement(element); // 设置图片属性,避免被InkCanvas选择系统处理 - image.IsHitTestVisible = true; - image.Focusable = false; + element.IsHitTestVisible = true; + element.Focusable = false; // 初始化InkCanvas选择设置 if (inkCanvas != null) @@ -3883,29 +3874,25 @@ namespace Ink_Canvas inkCanvas.EditingMode = InkCanvasEditingMode.None; } - inkCanvas.Children.Add(image); + inkCanvas.Children.Add(element); // 绑定事件处理器 - if (image is FrameworkElement elementForEvents) - { - // 鼠标事件 - elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown; - elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp; - elementForEvents.MouseMove += Element_MouseMove; - elementForEvents.MouseWheel += Element_MouseWheel; + element.MouseLeftButtonDown += Element_MouseLeftButtonDown; + element.MouseLeftButtonUp += Element_MouseLeftButtonUp; + element.MouseMove += Element_MouseMove; + element.MouseWheel += Element_MouseWheel; - // 触摸事件 - elementForEvents.TouchDown += Element_TouchDown; - elementForEvents.TouchUp += Element_TouchUp; - elementForEvents.IsManipulationEnabled = true; - elementForEvents.ManipulationDelta += Element_ManipulationDelta; - elementForEvents.ManipulationCompleted += Element_ManipulationCompleted; + // 触摸事件 + element.TouchDown += Element_TouchDown; + element.TouchUp += Element_TouchUp; + element.IsManipulationEnabled = true; + element.ManipulationDelta += Element_ManipulationDelta; + element.ManipulationCompleted += Element_ManipulationCompleted; - // 设置光标 - elementForEvents.Cursor = Cursors.Hand; - } + // 设置光标 + element.Cursor = Cursors.Hand; - timeMachine.CommitElementInsertHistory(image); + timeMachine.CommitElementInsertHistory(element); // 插入图片后切换到选择模式并刷新浮动栏高光显示 SetCurrentToolMode(InkCanvasEditingMode.Select); @@ -3920,32 +3907,29 @@ namespace Ink_Canvas { var dialog = new OpenFileDialog { - Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif" + Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf" }; if (dialog.ShowDialog() == true) { string filePath = dialog.FileName; - Image image = await CreateAndCompressImageAsync(filePath); // 补充image定义 - if (image != null) + FrameworkElement element = await CreateAndCompressImageAsync(filePath); + if (element != null) { string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; + element.Name = timestamp; // 初始化TransformGroup - if (image is FrameworkElement element) - { - var transformGroup = new TransformGroup(); - transformGroup.Children.Add(new ScaleTransform(1, 1)); - transformGroup.Children.Add(new TranslateTransform(0, 0)); - transformGroup.Children.Add(new RotateTransform(0)); - element.RenderTransform = transformGroup; - } + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(1, 1)); + transformGroup.Children.Add(new TranslateTransform(0, 0)); + transformGroup.Children.Add(new RotateTransform(0)); + element.RenderTransform = transformGroup; - CenterAndScaleElement(image); + CenterAndScaleElement(element); // 设置图片属性,避免被InkCanvas选择系统处理 - image.IsHitTestVisible = true; - image.Focusable = false; + element.IsHitTestVisible = true; + element.Focusable = false; // 初始化InkCanvas选择设置 if (inkCanvas != null) @@ -3956,29 +3940,25 @@ namespace Ink_Canvas inkCanvas.EditingMode = InkCanvasEditingMode.None; } - inkCanvas.Children.Add(image); + inkCanvas.Children.Add(element); // 绑定事件处理器 - if (image is FrameworkElement elementForEvents) - { - // 鼠标事件 - elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown; - elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp; - elementForEvents.MouseMove += Element_MouseMove; - elementForEvents.MouseWheel += Element_MouseWheel; + element.MouseLeftButtonDown += Element_MouseLeftButtonDown; + element.MouseLeftButtonUp += Element_MouseLeftButtonUp; + element.MouseMove += Element_MouseMove; + element.MouseWheel += Element_MouseWheel; - // 触摸事件 - elementForEvents.TouchDown += Element_TouchDown; - elementForEvents.TouchUp += Element_TouchUp; - elementForEvents.IsManipulationEnabled = true; - elementForEvents.ManipulationDelta += Element_ManipulationDelta; - elementForEvents.ManipulationCompleted += Element_ManipulationCompleted; + // 触摸事件 + element.TouchDown += Element_TouchDown; + element.TouchUp += Element_TouchUp; + element.IsManipulationEnabled = true; + element.ManipulationDelta += Element_ManipulationDelta; + element.ManipulationCompleted += Element_ManipulationCompleted; - // 设置光标 - elementForEvents.Cursor = Cursors.Hand; - } + // 设置光标 + element.Cursor = Cursors.Hand; - timeMachine.CommitElementInsertHistory(image); + timeMachine.CommitElementInsertHistory(element); // 插入图片后切换到选择模式并刷新浮动栏高光显示 SetCurrentToolMode(InkCanvasEditingMode.Select); diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs index 6ee5dc49..92ee533c 100644 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs @@ -1,3 +1,4 @@ +using Ink_Canvas.Controls; using Ink_Canvas.Helpers; using iNKORE.UI.WPF.Controls; using System; @@ -610,7 +611,7 @@ namespace Ink_Canvas // 检查是否有图片元素被选中(通过InkCanvas的选中元素) var selectedElements = inkCanvas.GetSelectedElements(); - bool hasImageElement = selectedElements.Any(element => element is Image); + bool hasImageElement = selectedElements.Any(element => element is Image || element is PdfEmbeddedView); // 如果有图片元素被选中,不显示选择框 if (hasImageElement) @@ -621,7 +622,7 @@ namespace Ink_Canvas } // 检查是否有图片元素被选中(通过currentSelectedElement) - if (currentSelectedElement != null && currentSelectedElement is Image) + if (currentSelectedElement != null && (currentSelectedElement is Image || currentSelectedElement is PdfEmbeddedView)) { GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; HideSelectionDisplay(); diff --git a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs index c3a4b9bf..6f8cfd6a 100644 --- a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs +++ b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs @@ -1,3 +1,4 @@ +using Ink_Canvas.Controls; using Ink_Canvas.Helpers; using Microsoft.Win32; using Newtonsoft.Json; @@ -101,7 +102,7 @@ namespace Ink_Canvas.Windows private double _collapsedOffset = 200; // 折叠时的偏移量(隐藏内容区域) private MainWindow _mainWindow; - private Dictionary _pptImages = new Dictionary(); + private Dictionary _pptImages = new Dictionary(); private Dictionary> _pptImagePaths = new Dictionary>(); @@ -183,9 +184,9 @@ namespace Ink_Canvas.Windows { foreach (var item in e.OldItems) { - if (item is System.Windows.Controls.Image image) + if (item is FrameworkElement fe && _pptImages.ContainsKey(fe)) { - RemoveImageFromPPT(image); + RemoveImageFromPPT(fe); } } } @@ -196,20 +197,20 @@ namespace Ink_Canvas.Windows } } - private void RemoveImageFromPPT(System.Windows.Controls.Image image) + private void RemoveImageFromPPT(FrameworkElement element) { try { - if (image == null) return; + if (element == null) return; - if (_pptImages.ContainsKey(image)) + if (_pptImages.ContainsKey(element)) { - int slideNumber = _pptImages[image]; - _pptImages.Remove(image); + int slideNumber = _pptImages[element]; + _pptImages.Remove(element); if (_pptImagePaths.ContainsKey(slideNumber)) { - string imagePath = image.Tag as string; + string imagePath = element.Tag as string; if (!string.IsNullOrEmpty(imagePath) && _pptImagePaths[slideNumber].Contains(imagePath)) { _pptImagePaths[slideNumber].Remove(imagePath); @@ -929,7 +930,7 @@ namespace Ink_Canvas.Windows { var dialog = new OpenFileDialog { - Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif" + Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf" }; if (dialog.ShowDialog() == true) @@ -941,14 +942,14 @@ namespace Ink_Canvas.Windows if (createImageMethod != null) { - var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task; + var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task; if (imageTask != null) { - var image = await imageTask; - if (image != null) + var inserted = await imageTask; + if (inserted != null) { - image.Tag = filePath; - await InsertImageToMainWindow(image, filePath); + inserted.Tag = filePath; + await InsertImageToMainWindow(inserted, filePath); } } } @@ -1185,9 +1186,9 @@ namespace Ink_Canvas.Windows }, DispatcherPriority.Normal); } - private async System.Threading.Tasks.Task InsertImageToMainWindow(System.Windows.Controls.Image image, string originalFilePath = null, bool saveToJson = true) + private async System.Threading.Tasks.Task InsertImageToMainWindow(FrameworkElement element, string originalFilePath = null, bool saveToJson = true) { - if (_mainWindow == null || image == null) return; + if (_mainWindow == null || element == null) return; // 确保在UI线程上执行 await Application.Current.Dispatcher.InvokeAsync(() => @@ -1196,11 +1197,11 @@ namespace Ink_Canvas.Windows { // 生成唯一名称 string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; + element.Name = timestamp; // 设置图片属性 - image.IsHitTestVisible = true; - image.Focusable = false; + element.IsHitTestVisible = true; + element.Focusable = false; System.Windows.Controls.InkCanvas inkCanvas = null; var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas", @@ -1274,7 +1275,7 @@ namespace Ink_Canvas.Windows // 如果在PPT模式下,记录图片和页面编号的关联,并保存图片路径 if (currentSlideNumber > 0 && !string.IsNullOrEmpty(originalFilePath) && saveToJson) { - _pptImages[image] = currentSlideNumber; + _pptImages[element] = currentSlideNumber; // 添加到页面图片路径列表 if (!_pptImagePaths.ContainsKey(currentSlideNumber)) @@ -1289,14 +1290,14 @@ namespace Ink_Canvas.Windows else if (currentSlideNumber > 0) { // 即使不保存到JSON,也要记录图片和页面编号的关联(用于翻页显示/隐藏) - _pptImages[image] = currentSlideNumber; + _pptImages[element] = currentSlideNumber; } // 先添加到画布(与MainWindow的实现保持一致) - inkCanvas.Children.Add(image); + inkCanvas.Children.Add(element); // 等待图片加载完成后再进行后续处理 - image.Loaded += (s, args) => + element.Loaded += (s, args) => { Application.Current.Dispatcher.BeginInvoke(new Action(() => { @@ -1305,19 +1306,19 @@ namespace Ink_Canvas.Windows // 初始化TransformGroup var initializeTransformMethod = _mainWindow.GetType().GetMethod("InitializeElementTransform", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - initializeTransformMethod?.Invoke(_mainWindow, new object[] { image }); + initializeTransformMethod?.Invoke(_mainWindow, new object[] { element }); // 居中缩放 var centerMethod = _mainWindow.GetType().GetMethod("CenterAndScaleElement", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - centerMethod?.Invoke(_mainWindow, new object[] { image }); + centerMethod?.Invoke(_mainWindow, new object[] { element }); // 绑定事件处理器 var bindEventsMethod = _mainWindow.GetType().GetMethod("BindElementEvents", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - bindEventsMethod?.Invoke(_mainWindow, new object[] { image }); + bindEventsMethod?.Invoke(_mainWindow, new object[] { element }); - LogHelper.WriteLogToFile($"图片插入完成: {image.Name}, PPT页面: {currentSlideNumber}"); + LogHelper.WriteLogToFile($"图片插入完成: {element.Name}, PPT页面: {currentSlideNumber}"); } catch (Exception ex) { @@ -1333,7 +1334,7 @@ namespace Ink_Canvas.Windows if (timeMachine != null) { var commitMethod = timeMachine.GetType().GetMethod("CommitElementInsertHistory"); - commitMethod?.Invoke(timeMachine, new object[] { image }); + commitMethod?.Invoke(timeMachine, new object[] { element }); } // 切换到选择模式 @@ -1699,12 +1700,16 @@ namespace Ink_Canvas.Windows // 检查已存在的图片路径(通过Tag) var existingImagePaths = new HashSet(); - foreach (System.Windows.Controls.Image existingImage in inkCanvas.Children.OfType()) + foreach (var existingImage in inkCanvas.Children.OfType()) { if (existingImage.Tag is string tagPath && !string.IsNullOrEmpty(tagPath)) - { existingImagePaths.Add(tagPath); - } + } + + foreach (var existingPdf in inkCanvas.Children.OfType()) + { + if (existingPdf.Tag is string tagPath && !string.IsNullOrEmpty(tagPath)) + existingImagePaths.Add(tagPath); } // 使用反射调用MainWindow的CreateAndCompressImageAsync方法 @@ -1733,17 +1738,17 @@ namespace Ink_Canvas.Windows continue; } - var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task; + var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task; if (imageTask != null) { - var image = await imageTask; - if (image != null) + var inserted = await imageTask; + if (inserted != null) { // 保存原始文件路径到Tag - image.Tag = imagePath; + inserted.Tag = imagePath; // 插入图片(不保存路径,因为已经存在) - await InsertImageToMainWindow(image, imagePath, false); + await InsertImageToMainWindow(inserted, imagePath, false); } } } diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json index cf179f10..d96e90d0 100644 --- a/Ink Canvas/packages.lock.json +++ b/Ink Canvas/packages.lock.json @@ -168,6 +168,12 @@ "System.Text.Json": "8.0.5" } }, + "System.Runtime.WindowsRuntime": { + "type": "Direct", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "RQxUkf37fp7MSWbOfKRjUjyudyfZb2u79YY5i1s+d7vuD80A7kmr2YfefO0JprQUhanxSm8bhXigCVfX2kEh+w==" + }, "WebDav.Client": { "type": "Direct", "requested": "[2.9.0, )", @@ -247,11 +253,6 @@ "System.Memory": "4.5.4" } }, - "System.ComponentModel.Annotations": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==" - }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", @@ -290,11 +291,6 @@ "resolved": "4.3.0", "contentHash": "J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==" }, - "System.Runtime.WindowsRuntime": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "IWrs1TmbxP65ZZjIglNyvDkFNoV5q2Pofg5WO7I8RKQOpLdFprQSh3xesOoClBqR4JHr4nEB1Xk1MqLPW1jPuQ==" - }, "System.Runtime.WindowsRuntime.UI.Xaml": { "type": "Transitive", "resolved": "4.6.0", @@ -352,19 +348,6 @@ "type": "Transitive", "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" - }, - "inkcanvas.pluginhost": { - "type": "Project", - "dependencies": { - "InkCanvas.PluginSdk": "[1.0.0, )" - } - }, - "inkcanvas.pluginsdk": { - "type": "Project", - "dependencies": { - "Newtonsoft.Json": "[13.0.3, )", - "System.ComponentModel.Annotations": "[5.0.0, )" - } } }, ".NETFramework,Version=v4.7.2/win": {