- Services/PdfRedactText.cs: strip text whose origin falls inside a CoverAnnotation from the page content stream at save time, hooked into PdfBurn.DrawAnnotationsIntoDoc. Fixes edited values staying recoverable by text extraction. - Themes/MMD.xaml replaces all thirteen themes; picker and accent strip removed; no dark mode. - Rename KillerPDF -> MMD PDF across code, resources, packaging and locale strings; new icon. - Remove the upstream author credit and the in-app install button.
213 lines
9.1 KiB
C#
213 lines
9.1 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Shapes;
|
|
using Docnet.Core;
|
|
using Docnet.Core.Models;
|
|
using Microsoft.Win32;
|
|
using PdfSharpCore.Drawing;
|
|
using PdfSharpCore.Pdf;
|
|
using PdfSharpCore.Pdf.IO;
|
|
using MmdPdf.Services;
|
|
using PdfPigDoc = UglyToad.PdfPig.PdfDocument;
|
|
|
|
namespace MmdPdf
|
|
{
|
|
public partial class MainWindow
|
|
{
|
|
// ============================================================
|
|
// Drag/drop: file open
|
|
// ============================================================
|
|
|
|
internal void DropZone_DragOver(object sender, DragEventArgs e)
|
|
{
|
|
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
|
e.Handled = true;
|
|
}
|
|
|
|
// internal: PdfViewer's XAML binds these three and forwards to them.
|
|
internal void DropZone_Drop(object sender, DragEventArgs e)
|
|
{
|
|
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
|
{
|
|
OnPathsDropped((string[])e.Data.GetData(DataFormats.FileDrop)!);
|
|
e.Handled = true; // don't let the same drop bubble to the window-level handler
|
|
}
|
|
}
|
|
|
|
internal void DropZone_Click(object sender, MouseButtonEventArgs e) => Open_Click(sender, e);
|
|
|
|
// ============================================================
|
|
// Drag/drop: page reorder
|
|
// ============================================================
|
|
|
|
private bool _pageDragArmed;
|
|
private void PageList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
_dragStartPoint = e.GetPosition(null);
|
|
// Only arm a page-reorder drag when the press lands on a page thumbnail, not the
|
|
// scrollbar - otherwise grabbing the scrollbar starts a page-move drag (the "insert"
|
|
// cursor) instead of scrolling.
|
|
_pageDragArmed = false;
|
|
for (var d = e.OriginalSource as DependencyObject; d != null; d = VisualTreeHelper.GetParent(d))
|
|
{
|
|
if (d is System.Windows.Controls.Primitives.ScrollBar) break;
|
|
if (d is ListBoxItem) { _pageDragArmed = true; break; }
|
|
}
|
|
}
|
|
|
|
private void PageList_PreviewMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (!_pageDragArmed || e.LeftButton != MouseButtonState.Pressed) return;
|
|
var diff = _dragStartPoint - e.GetPosition(null);
|
|
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
|
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
|
|
{
|
|
if (PageList.SelectedIndex >= 0)
|
|
DragDrop.DoDragDrop(PageList, PageList.SelectedIndex, DragDropEffects.Move);
|
|
}
|
|
}
|
|
|
|
private void PageList_DragOver(object sender, DragEventArgs e)
|
|
{
|
|
// #172: files dropped onto the Pages sidebar append to the open document,
|
|
// so the list accepts FileDrop as well as its own page-reorder payload.
|
|
if (e.Data.GetDataPresent(typeof(int)))
|
|
e.Effects = DragDropEffects.Move;
|
|
else if (_doc != null && DroppedOpenablePaths(e).Length > 0)
|
|
e.Effects = DragDropEffects.Copy;
|
|
else
|
|
e.Effects = DragDropEffects.None;
|
|
e.Handled = true;
|
|
}
|
|
|
|
private static string[] DroppedOpenablePaths(DragEventArgs e)
|
|
=> e.Data.GetDataPresent(DataFormats.FileDrop)
|
|
? ((string[])e.Data.GetData(DataFormats.FileDrop)!).Where(IsOpenablePath).ToArray()
|
|
: [];
|
|
|
|
// #172: append the dropped files' pages to the open document. Appending (not inserting at
|
|
// the drop point) keeps existing page indices stable, so annotations and rotations need no
|
|
// remapping.
|
|
private async void AppendFilesToCurrentDoc(string[] files)
|
|
{
|
|
if (_doc is null) return;
|
|
CommitActiveTextBox();
|
|
int before = _doc.PageCount;
|
|
foreach (var f in files)
|
|
{
|
|
if (PdfImport.IsPdfPath(f))
|
|
{
|
|
var target = _doc;
|
|
if (target != null && TryAppendPdfPages(target, f)) continue;
|
|
|
|
// #203: a damaged PDF used to be swallowed here, so nothing was added and
|
|
// nothing was said. Offer the same repair the open path offers.
|
|
string? repaired = await RepairDroppedPdfAsync(f);
|
|
if (repaired != null && _doc != null) TryAppendPdfPages(_doc, repaired);
|
|
}
|
|
else
|
|
{
|
|
var target = _doc;
|
|
if (target != null)
|
|
try { PdfImport.AddImagePagesFromFile(target, f); } catch { /* skip an unreadable image */ }
|
|
}
|
|
}
|
|
if (_doc is null) return;
|
|
if (_doc.PageCount == before) { SetStatus(Loc("Str_Drop_NothingOpenable")); return; }
|
|
MarkDirty(true);
|
|
SaveTempAndReload(keepAnnotations: true, preserveZoom: true);
|
|
SetStatus(string.Format(Loc("Str_Status_Merged"), files.Length));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import-mode page copy. False when the file cannot be read at all, which is the signal
|
|
/// to offer a repair rather than silently dropping it.
|
|
/// </summary>
|
|
private static bool TryAppendPdfPages(PdfDocument target, string path)
|
|
{
|
|
try
|
|
{
|
|
using var src = PdfReader.Open(path, PdfDocumentOpenMode.Import);
|
|
if (src.PageCount == 0) return false;
|
|
for (int i = 0; i < src.PageCount; i++) target.AddPage(src.Pages[i]);
|
|
return true;
|
|
}
|
|
catch { return false; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs the open path's three repair strategies against a dropped file and returns the
|
|
/// repaired temp copy, or null if the user declined or nothing recovered it. The original
|
|
/// file is never written to.
|
|
/// </summary>
|
|
private async System.Threading.Tasks.Task<string?> RepairDroppedPdfAsync(string path)
|
|
{
|
|
var ask = KillerDialog.Show(this,
|
|
string.Format(Loc("Str_Dlg_RepairAsk"), System.IO.Path.GetFileName(path)),
|
|
"MmdPdf", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
|
if (ask != MessageBoxResult.Yes) return null;
|
|
|
|
var busy = ShowBusyOverlay(Loc("Str_Busy_Repairing"));
|
|
try
|
|
{
|
|
// Same order as TryRepairAndOpen: lossless PDFium re-save first (keeps forms and
|
|
// bookmarks), then a PdfSharpCore page-copy, then the rasterize that always
|
|
// produces something openable.
|
|
string? repaired = await System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
var p = App.MakeTempFile("repaired");
|
|
return PdfiumInterop.TryPdfiumStripEncryption(path, p) ? p : null;
|
|
});
|
|
repaired ??= await System.Threading.Tasks.Task.Run(() => PdfImport.RepairViaImportToFile(path));
|
|
repaired ??= await System.Threading.Tasks.Task.Run(() => PdfImport.RepairViaDocnetRasterizeToFile(path));
|
|
|
|
if (repaired is null)
|
|
KillerDialog.Show(this,
|
|
$"\"{System.IO.Path.GetFileName(path)}\" could not be repaired.",
|
|
"MmdPdf", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
return repaired;
|
|
}
|
|
finally { HideBusyOverlay(busy); }
|
|
}
|
|
|
|
private void PageList_Drop(object sender, DragEventArgs e)
|
|
{
|
|
if (_doc != null && !e.Data.GetDataPresent(typeof(int)))
|
|
{
|
|
var files = DroppedOpenablePaths(e);
|
|
if (files.Length > 0) { AppendFilesToCurrentDoc(files); e.Handled = true; return; }
|
|
}
|
|
if (_doc is null || !e.Data.GetDataPresent(typeof(int))) return;
|
|
var doc = _doc;
|
|
int fromIdx = (int)e.Data.GetData(typeof(int))!;
|
|
var pos = e.GetPosition(PageList);
|
|
int toIdx = PageList.Items.Count - 1;
|
|
for (int i = 0; i < PageList.Items.Count; i++)
|
|
{
|
|
if (PageList.ItemContainerGenerator.ContainerFromIndex(i) is ListBoxItem item)
|
|
{
|
|
var itemPos = item.TranslatePoint(new Point(0, item.ActualHeight / 2), PageList);
|
|
if (pos.Y < itemPos.Y) { toIdx = i; break; }
|
|
}
|
|
}
|
|
if (fromIdx == toIdx) return;
|
|
var page = doc.Pages[fromIdx];
|
|
doc.Pages.RemoveAt(fromIdx);
|
|
if (toIdx > fromIdx) toIdx--;
|
|
doc.Pages.Insert(toIdx, page);
|
|
SaveTempAndReload();
|
|
PageList.SelectedIndex = toIdx;
|
|
}
|
|
}
|
|
}
|