feat: rebrand to MMD PDF, single corporate theme, and remove original text under an edit

- 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.
This commit is contained in:
2026-08-27 07:37:09 +02:00
parent 532485a830
commit 6610acfa44
230 changed files with 9362 additions and 11508 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
using System.Windows;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// #169: a plain page rotation used to reload with SaveTempAndReload's keepAnnotations
// default, which cleared every overlay annotation - committed, unsaved user work was
+3 -3
View File
@@ -4,11 +4,11 @@ using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Raw-bitmap helpers - pure functions over BGRA pixel buffers,
// no window state. Formerly a MainWindow partial (KillerUI
// no window state. Formerly a MainWindow partial (MmdPdfUI
// refactor); shared by the render paths, thumbnails, page
// export, OCR and the CLI.
// ============================================================
@@ -180,7 +180,7 @@ namespace KillerPDF.Services
/// <summary>
/// Encodes raw BGRA pixel data to JPEG (quality 90) via WPF's encoder. Born as the CLI's
/// CliEncodeJpeg (no JPEG encoder existed before --to-image); homed here beside RenderToPng
/// in the KillerUI refactor, shared by the CLI and the GUI image export.
/// in the MmdPdfUI refactor, shared by the CLI and the GUI image export.
/// </summary>
internal static byte[] EncodeJpeg(byte[] bgra, int width, int height, double dpi = 96)
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Minimal 'cmap' reader: answers "does this face have a glyph for this
+4 -4
View File
@@ -4,10 +4,10 @@ using System.IO;
using System.Reflection;
using System.Text;
namespace KillerPDF
namespace MmdPdf
{
/// <summary>
/// Writes structured crash logs to %LOCALAPPDATA%\KillerPDF\Logs\ and maintains
/// Writes structured crash logs to %LOCALAPPDATA%\MmdPdf\Logs\ and maintains
/// a rolling buffer of recent status-bar messages for post-mortem context.
/// </summary>
internal static class CrashReporter
@@ -21,7 +21,7 @@ namespace KillerPDF
internal static string LogDir { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KillerPDF", "Logs");
"MmdPdf", "Logs");
/// <summary>Path of the log file written by the most recent Capture() call.</summary>
internal static string? LastLogPath { get; private set; }
@@ -55,7 +55,7 @@ namespace KillerPDF
var sb = new StringBuilder();
var ver = Assembly.GetExecutingAssembly().GetName().Version;
sb.AppendLine($"KillerPDF v{ver?.ToString(3)} crash report");
sb.AppendLine($"MmdPdf v{ver?.ToString(3)} crash report");
sb.AppendLine($"Time : {DateTime.Now:yyyy-MM-dd HH:mm:ss zzz}");
sb.AppendLine($"OS : {Environment.OSVersion}");
sb.AppendLine($"CLR : {Environment.Version}");
+1 -1
View File
@@ -1,4 +1,4 @@
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Glyph coverage + the fallback chain (#168).
+1 -1
View File
@@ -2,7 +2,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Input;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Keyboard-layout aware shortcut matching (#153).
+3 -3
View File
@@ -2,7 +2,7 @@ using System;
using System.IO;
using System.Windows;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal enum Locale { EnUS, Es, ZhTW, ZhCN, Bn, TrTR, De, Fr, JaJP, CsCZ, PlPL, HuHU }
@@ -66,8 +66,8 @@ namespace KillerPDF.Services
{
if (TryApplyExternal()) { EnsureWatcher(); return; }
if (!_externalLoadedOnce)
MessageBox.Show($"Could not load the translation file:\n{ExternalFile}\n\nCheck the path and the file's XML, then start KillerPDF again.",
"KillerPDF --lang-file", MessageBoxButton.OK, MessageBoxImage.Warning);
MessageBox.Show($"Could not load the translation file:\n{ExternalFile}\n\nCheck the path and the file's XML, then start MmdPdf again.",
"MmdPdf --lang-file", MessageBoxButton.OK, MessageBoxImage.Warning);
// Fall through to the normal locale so the app still comes up usable.
}
+4 -4
View File
@@ -1,19 +1,19 @@
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// OCR catalog - pure data, no IO, no App, no bootstrap.
// ============================================================
//
// Split out of OcrLanguages.cs so the test project can link THIS file alone. KillerPDF.Tests
// Split out of OcrLanguages.cs so the test project can link THIS file alone. MmdPdf.Tests
// compiles individual sources rather than referencing the app assembly, and OcrLanguages
// reaches App.GetSetting, OcrNativeBootstrap and HttpClient - none of which a data check needs.
//
// THE RULE THIS FILE ENCODES: OCR languages track interface languages. If KillerPDF ships a UI
// THE RULE THIS FILE ENCODES: OCR languages track interface languages. If MmdPdf ships a UI
// in a language, it ships an OCR model for that language. There is no interface language whose
// text the app cannot read.
//
// It drifted once already, because nothing checked: the UI shipped hu-HU while the catalog
// stayed at eleven entries, and killerpdf.net kept claiming OCR covered ten languages and that
// stayed at eleven entries, and baobab-ts.com kept claiming OCR covered ten languages and that
// Polish and Hungarian did not need models. OcrCatalogTests now reads Strings\*.xaml and fails
// the build if these lists and that folder disagree, and release.ps1 runs the suite.
internal static class OcrCatalog
+3 -3
View File
@@ -2,12 +2,12 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// OCR languages - the catalog, install checks and traineddata
// downloads. Pure helpers over files, settings and HTTP; no
// window state. Split out of Ocr.cs (KillerUI refactor).
// window state. Split out of Ocr.cs (MmdPdfUI refactor).
// ============================================================
internal static class OcrLanguages
{
@@ -58,7 +58,7 @@ namespace KillerPDF.Services
// Timeout covers connect + headers; the body is bounded by the cancellation token instead.
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
var http = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(100) };
http.DefaultRequestHeaders.UserAgent.ParseAdd("KillerPDF-OCR");
http.DefaultRequestHeaders.UserAgent.ParseAdd("MmdPdf-OCR");
return http;
}
+5 -5
View File
@@ -2,7 +2,7 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
/// <summary>
/// Keeps the single-exe build self-sufficient for OCR. The native Tesseract DLLs (x64) and the bundled
@@ -12,8 +12,8 @@ namespace KillerPDF.Services
/// </summary>
internal static class OcrNativeBootstrap
{
private const string NativePrefix = "KillerPDF.OcrNative.";
private const string TessDataPrefix = "KillerPDF.OcrTessData.";
private const string NativePrefix = "MmdPdf.OcrNative.";
private const string TessDataPrefix = "MmdPdf.OcrTessData.";
private static readonly object _gate = new();
private static bool _langReady;
@@ -31,7 +31,7 @@ namespace KillerPDF.Services
/// </summary>
public static string TessDataDir { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KillerPDF", "tessdata");
"MmdPdf", "tessdata");
/// <summary>
/// Ensures the bundled language data (English) is present in <see cref="TessDataDir"/> and returns
@@ -77,7 +77,7 @@ namespace KillerPDF.Services
string version = asm.GetName().Version?.ToString() ?? "0";
string baseDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KillerPDF", "ocr", version);
"MmdPdf", "ocr", version);
string nativeDir = Path.Combine(baseDir, "x64");
Directory.CreateDirectory(nativeDir);
+1 -1
View File
@@ -2,7 +2,7 @@ using System.IO;
using System.Windows.Media.Imaging;
using Tesseract;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
/// <summary>A single recognized word with its confidence and pixel box (top-left origin, OCR image space).</summary>
internal sealed class OcrWord
+38 -3
View File
@@ -3,15 +3,17 @@ using System.Windows;
using System.Windows.Media;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using MmdPdf;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Burn-to-document core - draws the overlay annotation layer
// and the stamp layer (page numbers / watermark) into a
// PdfDocument via XGraphics, in PDF-point space. Static and
// window-free by design (split out of Shell/Annotations.cs and
// Shell/Stamps.cs in the KillerUI refactor): the print flow
// Shell/Stamps.cs in the MmdPdfUI refactor): the print flow
// runs these on a background thread against a throwaway copy
// of the document, and being static means the compiler
// guarantees no live UI state is touched.
@@ -79,7 +81,7 @@ namespace KillerPDF.Services
private static int NormalizeRot(IReadOnlyDictionary<int, int>? rotations, int pageIdx, PdfPage page)
{
// KillerPDF-managed rotations live in the map after SaveTempAndReload strips them
// MmdPdf-managed rotations live in the map after SaveTempAndReload strips them
// from the working file. A newly opened PDF still carries its native /Rotate on the
// page, so use that value whenever the map has no entry for this page.
int r = rotations is not null && rotations.TryGetValue(pageIdx, out int stored)
@@ -128,6 +130,39 @@ namespace KillerPDF.Services
double sx = visW / renderW;
double sy = visH / renderH;
// MMD: an existing-text edit lays an opaque CoverAnnotation over the original
// run and draws the replacement on top - which left the original glyphs in the
// content stream, so text extraction still returned the old value. Strip them
// here, BEFORE appending our own content, so an edited figure is really gone.
{
var coverRects = new System.Collections.Generic.List<XRect>();
var visToPage = VisualToPageMatrix(rot, pwPt, phPt);
foreach (var cand in annots)
{
if (cand is not CoverAnnotation cov) continue;
var cr = cov.DrawRect();
double gx = cr.X * sx, gy = cr.Y * sy, gw = cr.Width * sx, gh = cr.Height * sy;
var corners = new[]
{
new XPoint(gx, gy), new XPoint(gx + gw, gy),
new XPoint(gx, gy + gh), new XPoint(gx + gw, gy + gh)
};
if (visToPage is XMatrix vm)
for (int ci = 0; ci < corners.Length; ci++) corners[ci] = vm.Transform(corners[ci]);
double minX = corners[0].X, maxX = corners[0].X, minY = corners[0].Y, maxY = corners[0].Y;
foreach (var pt in corners)
{
if (pt.X < minX) minX = pt.X;
if (pt.X > maxX) maxX = pt.X;
if (pt.Y < minY) minY = pt.Y;
if (pt.Y > maxY) maxY = pt.Y;
}
// XGraphics space is y-down from the top-left; PDF user space is y-up.
coverRects.Add(new XRect(minX, phPt - maxY, maxX - minX, maxY - minY));
}
if (coverRects.Count > 0) PdfRedactText.RemoveTextUnder(page, coverRects);
}
using var gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Append);
// ...then turn the whole drawing back into the page's own frame, so every draw
// call below can keep working in visual coordinates exactly as it always has.
+1 -1
View File
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal readonly record struct DetectedPdfFontStyle(string Family, bool Bold, bool Italic);
+1 -1
View File
@@ -3,7 +3,7 @@ using PdfSharpCore.Drawing;
using PdfSharpCore.Fonts;
using SixLabors.Fonts;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Font resolution for the SAVE path (#168).
+1 -1
View File
@@ -1,6 +1,6 @@
using PdfPigDoc = UglyToad.PdfPig.PdfDocument;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Image placement extraction for the display dark mode (#135
+2 -2
View File
@@ -8,12 +8,12 @@ using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.IO;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// File import/repair helpers - pure functions over paths and
// PdfDocuments, no window state. Split out of FileOperations.cs
// and ImportAndZip.cs (KillerUI refactor); shared by the GUI
// and ImportAndZip.cs (MmdPdfUI refactor); shared by the GUI
// open/merge/repair paths, TempReload, and the CLI. The one
// import helper NOT here is TryPdfiumStripEncryption - it rides
// the shared PDFium interop block (and its lock), which stays
+2 -2
View File
@@ -1,11 +1,11 @@
using PdfSharpCore.Pdf;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Outline/bookmark document helpers - pure functions over the
// PdfSharpCore outline tree, no window state. Split out of
// SidebarOutline.cs (KillerUI refactor); the TreeView panel and
// SidebarOutline.cs (MmdPdfUI refactor); the TreeView panel and
// bookmark editing UI stay in the shell.
// ============================================================
internal static class PdfOutlines
+2 -2
View File
@@ -5,11 +5,11 @@ using Docnet.Core.Models;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Document rasterization cores - pure functions over a rendered
// source file, no window state (KillerUI refactor; split out of
// source file, no window state (MmdPdfUI refactor; split out of
// FileOperations.cs' Save Flattened and Export Images flows).
// The shell keeps the dialogs, the annotation burn, and the
// progress overlay; progress arrives via callback, cancel via
+211
View File
@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.Content;
using PdfSharpCore.Pdf.Content.Objects;
namespace MmdPdf.Services
{
// ============================================================
// Removes original page text that sits under an opaque cover.
//
// When a user edits existing text, the editor lays an opaque CoverAnnotation
// over the original run and draws the replacement on top. The page LOOKED
// right but the original glyphs were still in the content stream, so text
// extraction and copy/paste returned the old value alongside the new one -
// an edited quantity on a delivery note stayed recoverable.
//
// This pass walks the page content stream and blanks every text-showing
// operator whose text origin falls inside a cover rectangle, so the old
// glyphs are gone from the file, not merely hidden. Positioning operators
// are left untouched, so nothing else on the page moves.
//
// Deliberate limitation: a run is removed only when its ORIGIN is inside the
// cover. A single show-text operator that starts outside the cover and runs
// into it is left alone rather than risk deleting text the user did not edit.
// The editor's own covers are generated to bound the run they replace, so
// this is exact for edits made in this application.
// ============================================================
internal static class PdfRedactText
{
/// <summary>
/// Blanks text drawn with its origin inside any of <paramref name="rectsPdf"/>.
/// Rectangles are in PDF user space (origin bottom-left, y up), points.
/// Returns the number of show-text operators blanked.
/// </summary>
internal static int RemoveTextUnder(PdfPage page, IReadOnlyList<XRect> rectsPdf)
{
if (page is null || rectsPdf is null || rectsPdf.Count == 0) return 0;
CSequence content;
try { content = ContentReader.ReadContent(page); }
catch { return 0; } // unparsable stream: leave the page exactly as it was
var state = new TextState();
int blanked = Walk(content, state, rectsPdf);
if (blanked == 0) return 0;
try { page.Contents.ReplaceContent(content); }
catch { return 0; }
return blanked;
}
// ── graphics + text state ──────────────────────────────────────────
private sealed class TextState
{
internal XMatrix Ctm = XMatrix.Identity;
internal readonly Stack<XMatrix> CtmStack = new Stack<XMatrix>();
internal XMatrix Tm = XMatrix.Identity; // text matrix
internal XMatrix Tlm = XMatrix.Identity; // text line matrix
internal double Leading; // TL
}
private static int Walk(CSequence seq, TextState st, IReadOnlyList<XRect> rects)
{
int blanked = 0;
foreach (var obj in seq)
{
if (obj is CSequence inner && obj is not CArray)
{
blanked += Walk(inner, st, rects);
continue;
}
if (obj is not COperator op) continue;
string name = op.OpCode?.Name ?? "";
switch (name)
{
case "q":
st.CtmStack.Push(st.Ctm);
break;
case "Q":
if (st.CtmStack.Count > 0) st.Ctm = st.CtmStack.Pop();
break;
case "cm":
if (TryMatrix(op.Operands, out var cm)) st.Ctm = cm * st.Ctm;
break;
case "BT":
st.Tm = st.Tlm = XMatrix.Identity;
break;
case "ET":
st.Tm = st.Tlm = XMatrix.Identity;
break;
case "Tm":
if (TryMatrix(op.Operands, out var tm)) st.Tm = st.Tlm = tm;
break;
case "TL":
st.Leading = Num(op.Operands, 0);
break;
case "Td":
NextLine(st, Num(op.Operands, 0), Num(op.Operands, 1));
break;
case "TD":
st.Leading = -Num(op.Operands, 1);
NextLine(st, Num(op.Operands, 0), Num(op.Operands, 1));
break;
case "T*":
NextLine(st, 0, -st.Leading);
break;
case "'":
NextLine(st, 0, -st.Leading);
blanked += BlankIfInside(op, st, rects);
break;
case "\"":
NextLine(st, 0, -st.Leading);
blanked += BlankIfInside(op, st, rects);
break;
case "Tj":
case "TJ":
blanked += BlankIfInside(op, st, rects);
break;
}
}
return blanked;
}
private static void NextLine(TextState st, double tx, double ty)
{
var t = new XMatrix(1, 0, 0, 1, tx, ty);
st.Tlm = t * st.Tlm;
st.Tm = st.Tlm;
}
private static int BlankIfInside(COperator op, TextState st, IReadOnlyList<XRect> rects)
{
var m = st.Tm * st.Ctm;
var origin = new XPoint(m.OffsetX, m.OffsetY);
bool hit = false;
for (int i = 0; i < rects.Count; i++)
{
var r = rects[i];
// A baseline sits slightly above the visual bottom of the glyphs, so allow a
// small pad below the cover; otherwise a run whose baseline is a point under
// the cover edge survives.
if (origin.X >= r.X - 1.0 && origin.X <= r.X + r.Width + 1.0 &&
origin.Y >= r.Y - 2.0 && origin.Y <= r.Y + r.Height + 2.0)
{
hit = true;
break;
}
}
if (!hit) return 0;
return BlankStrings(op.Operands) ? 1 : 0;
}
private static bool BlankStrings(CSequence operands)
{
if (operands is null) return false;
bool any = false;
for (int i = 0; i < operands.Count; i++)
{
switch (operands[i])
{
case CString s when s.Value.Length > 0:
s.Value = "";
any = true;
break;
case CArray arr:
for (int j = 0; j < arr.Count; j++)
{
if (arr[j] is CString cs && cs.Value.Length > 0)
{
cs.Value = "";
any = true;
}
}
break;
}
}
return any;
}
// ── operand helpers ────────────────────────────────────────────────
private static double Num(CSequence operands, int index)
{
if (operands is null || index >= operands.Count) return 0;
return operands[index] switch
{
CInteger i => i.Value,
CReal r => r.Value,
_ => 0
};
}
private static bool TryMatrix(CSequence operands, out XMatrix m)
{
m = XMatrix.Identity;
if (operands is null || operands.Count < 6) return false;
m = new XMatrix(Num(operands, 0), Num(operands, 1), Num(operands, 2),
Num(operands, 3), Num(operands, 4), Num(operands, 5));
return true;
}
}
}
+3 -3
View File
@@ -1,11 +1,11 @@
using PdfSharpCore.Pdf;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Pre-save document scrubs - pure functions over a PdfDocument,
// no window state. Split out of FileOperations.cs and Links.cs
// (KillerUI refactor); shared by the GUI save paths, TempReload,
// (MmdPdfUI refactor); shared by the GUI save paths, TempReload,
// the CLI runner and the batch runner.
// ============================================================
internal static class PdfScrub
@@ -154,7 +154,7 @@ namespace KillerPDF.Services
return false;
}
// A KillerPDF save fully REWRITES the file, which mathematically invalidates any existing
// A MmdPdf save fully REWRITES the file, which mathematically invalidates any existing
// digital signature: its /ByteRange and digest describe the old bytes (ISO 19005-2, 6.4.3
// requires the digest to cover the entire file). Carrying the dead signature forward
// misleads viewers and fails PDF/A validation, so strip signature VALUES (/V) from
+4 -4
View File
@@ -2,11 +2,11 @@ using System.IO;
using System.Runtime.InteropServices;
using Docnet.Core;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// Direct PDFium P/Invoke - the ONE home for every direct
// pdfium.dll call in the app (KillerUI refactor; formerly split
// pdfium.dll call in the app (MmdPdfUI refactor; formerly split
// across FileOperations.cs and Links.cs).
//
// THREADING: PDFium is single-threaded. Docnet serializes every
@@ -410,7 +410,7 @@ namespace KillerPDF.Services
var doc = FPDF_LoadDocument(sourcePath, null);
if (doc == IntPtr.Zero)
{
try { File.AppendAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "killerpdf_pdfium_debug.txt"), $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] FPDF_LoadDocument returned null for: {sourcePath}\n\n"); } catch { }
try { File.AppendAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "mmdpdf_pdfium_debug.txt"), $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] FPDF_LoadDocument returned null for: {sourcePath}\n\n"); } catch { }
return false;
}
try
@@ -459,7 +459,7 @@ namespace KillerPDF.Services
try
{
File.AppendAllText(
System.IO.Path.Combine(System.IO.Path.GetTempPath(), "killerpdf_pdfium_debug.txt"),
System.IO.Path.Combine(System.IO.Path.GetTempPath(), "mmdpdf_pdfium_debug.txt"),
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] TryPdfiumSaveWithZeroRotations failed\n" +
$" source: {sourcePath}\n" +
$" type: {ex.GetType().FullName}\n" +
+1 -1
View File
@@ -4,7 +4,7 @@ using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal static class PerspectiveWarp
{
+5 -5
View File
@@ -2,12 +2,12 @@ using System;
using System.Diagnostics;
using Microsoft.Win32;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal static class ProtocolRegistrar
{
internal const string Scheme = "killerpdf";
private const string RegistryPath = @"Software\Classes\killerpdf";
internal const string Scheme = "mmdpdf";
private const string RegistryPath = @"Software\Classes\mmdpdf";
internal static void Register() => Register(Registry.CurrentUser, null);
@@ -22,14 +22,14 @@ namespace KillerPDF.Services
appPath ??= Process.GetCurrentProcess().MainModule!.FileName;
using var protocol = root.CreateSubKey(RegistryPath);
if (protocol == null) return;
protocol.SetValue("", "URL:KillerPDF Protocol");
protocol.SetValue("", "URL:MmdPdf Protocol");
protocol.SetValue("URL Protocol", "");
using (var icon = protocol.CreateSubKey("DefaultIcon"))
icon?.SetValue("", $"\"{appPath}\",0");
using (var command = protocol.CreateSubKey(@"shell\open\command"))
command?.SetValue("", $"\"{appPath}\" \"%1\"");
}
catch (Exception ex) { Debug.WriteLine($"Failed to register KillerPDF protocol: {ex.Message}"); }
catch (Exception ex) { Debug.WriteLine($"Failed to register MmdPdf protocol: {ex.Message}"); }
}
internal static void Unregister() => Unregister(Registry.CurrentUser);
+1 -1
View File
@@ -1,6 +1,6 @@
using UglyToad.PdfPig;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal sealed class SearchResult
{
+2 -2
View File
@@ -4,11 +4,11 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
// ============================================================
// File-type (shell) icons - split out of FileOperations.cs
// (KillerUI refactor). Same name as the KillerUI kit's
// (MmdPdfUI refactor). Same name as the MmdPdfUI kit's
// Services/ShellIcons.cs, which the family file picker uses, so
// the picker rollout lands on a familiar shape.
//
+2 -2
View File
@@ -1,6 +1,6 @@
using System.Linq;
namespace KillerPDF
namespace MmdPdf
{
// ============================================================
// THE shortcut table. One source of truth for both views of the shortcuts overlay: the list
@@ -12,7 +12,7 @@ namespace KillerPDF
// another while the code did neither consistently. Deriving both views from one array means a
// binding cannot be described two ways, and ShortcutTableTests holds that.
//
// Deliberately free of WPF and of MainWindow, so KillerPDF.Tests can link the file the way it
// Deliberately free of WPF and of MainWindow, so MmdPdf.Tests can link the file the way it
// links OcrCatalog.cs. Anything that needs a Brush or a Control belongs in the overlay files.
// ============================================================
+2 -2
View File
@@ -1,6 +1,6 @@
using System.Text.Json;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal sealed class SignatureStore
{
@@ -9,7 +9,7 @@ namespace KillerPDF.Services
private static readonly string DefaultDir = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KillerPDF");
"MmdPdf");
public SignatureStore()
: this(DefaultDir, System.IO.Path.Combine(DefaultDir, "signatures.json")) { }
+1 -1
View File
@@ -1,6 +1,6 @@
using System.Security.Cryptography.X509Certificates;
namespace KillerPDF.Services.Signing
namespace MmdPdf.Services.Signing
{
/// <summary>
/// A source of a signing certificate (with its private key). Kept as an interface so the only
+1 -1
View File
@@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using PdfSharp.Pdf.Signatures;
namespace KillerPDF.Services.Signing
namespace MmdPdf.Services.Signing
{
/// <summary>
/// A PDF signer that assembles the PKCS#7 / CMS itself with .NET's <see cref="SignedCms"/> rather
+2 -2
View File
@@ -5,11 +5,11 @@ using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Signatures;
namespace KillerPDF.Services.Signing
namespace MmdPdf.Services.Signing
{
/// <summary>
/// Cryptographic (PAdES / PKCS#7) PDF signing, isolated from the rest of the app. Everything else
/// in KillerPDF uses PdfSharpCore; this module uses PDFsharp 6.2 (the <c>PdfSharp.*</c> namespace),
/// in MmdPdf uses PdfSharpCore; this module uses PDFsharp 6.2 (the <c>PdfSharp.*</c> namespace),
/// and the two coexist without clashing. There are deliberately no WPF or Windows-only types here,
/// so the whole module ports to Avalonia / Linux / Mac unchanged.
///
@@ -1,7 +1,7 @@
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace KillerPDF.Services.Signing
namespace MmdPdf.Services.Signing
{
/// <summary>
/// Loads a signing certificate from a .pfx / .p12 file plus its password. Fully cross-platform,
+1 -1
View File
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
namespace KillerPDF.Services.Signing
namespace MmdPdf.Services.Signing
{
/// <summary>
/// Windows-only helper that lists signing-capable certificates from the current user's personal
+4 -4
View File
@@ -4,15 +4,15 @@ using System.Globalization;
using System.IO;
using System.Text;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
/// <summary>
/// Opt-in startup timing used by the release benchmark. Normal launches do no file I/O.
/// Set KILLERPDF_STARTUP_TRACE to an output path before starting the process.
/// Set MMDPDF_STARTUP_TRACE to an output path before starting the process.
/// </summary>
internal static class StartupTrace
{
private const string TraceEnvironmentVariable = "KILLERPDF_STARTUP_TRACE";
private const string TraceEnvironmentVariable = "MMDPDF_STARTUP_TRACE";
private static readonly object Gate = new object();
private static readonly Stopwatch Clock = Stopwatch.StartNew();
private static readonly string? OutputPath = Environment.GetEnvironmentVariable(TraceEnvironmentVariable);
@@ -36,7 +36,7 @@ namespace KillerPDF.Services
{
_headerWritten = true;
var process = Process.GetCurrentProcess();
sb.Append("# KillerPDF startup trace | pid=")
sb.Append("# MmdPdf startup trace | pid=")
.Append(process.Id)
.Append(" | processStartUtc=")
.Append(process.StartTime.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture))
+1 -1
View File
@@ -1,7 +1,7 @@
using System.IO;
using UglyToad.PdfPig;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
/// <summary>One selectable character on a page, in reading order. Coordinates are PDF space
/// (points, bottom-left origin), matching SearchService and ExtractTextFromRegion.</summary>
+18 -16
View File
@@ -7,7 +7,7 @@ using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Threading;
namespace KillerPDF.Services
namespace MmdPdf.Services
{
internal enum Theme
{
@@ -37,7 +37,7 @@ namespace KillerPDF.Services
private static DarkAccent _darkAccent = DarkAccent.Green;
private static DarkAccent _lightAccent = DarkAccent.Green;
private static DarkAccent _blackAccent = DarkAccent.Green;
// Match the shared KillerTools 98SE palette: classic Win98 navy is the default.
// Match the shared MMD Group IT 98SE palette: classic Win98 navy is the default.
private static DarkAccent _se98Accent = DarkAccent.Blue;
public static Theme Current => _current;
@@ -134,19 +134,21 @@ namespace KillerPDF.Services
{
var uri = theme switch
{
Theme.Light => new Uri("pack://application:,,,/Themes/Light.xaml"),
Theme.Black => new Uri("pack://application:,,,/Themes/Black.xaml"),
Theme.SE98 => new Uri("pack://application:,,,/Themes/98SE.xaml"),
Theme.Blood => new Uri("pack://application:,,,/Themes/Blood.xaml"),
Theme.Greed => new Uri("pack://application:,,,/Themes/Greed.xaml"),
Theme.Cyanotic => new Uri("pack://application:,,,/Themes/Cyanotic.xaml"),
Theme.Ectoplasm => new Uri("pack://application:,,,/Themes/Ectoplasm.xaml"),
Theme.Decay => new Uri("pack://application:,,,/Themes/Decay.xaml"),
Theme.Mourning => new Uri("pack://application:,,,/Themes/Mourning.xaml"),
Theme.Sepulchre => new Uri("pack://application:,,,/Themes/Sepulchre.xaml"),
Theme.Delirium => new Uri("pack://application:,,,/Themes/Delirium.xaml"),
Theme.Malaise => new Uri("pack://application:,,,/Themes/Malaise.xaml"),
_ => new Uri("pack://application:,,,/Themes/Dark.xaml"),
// MMD PDF ships exactly one theme. Every value resolves here so no stored
// preference, and no code path, can put a dark or novelty skin on screen.
_ => new Uri("pack://application:,,,/Themes/MMD.xaml"),
};
var newDict = new ResourceDictionary { Source = uri };
@@ -198,7 +200,7 @@ namespace KillerPDF.Services
string sub = theme == Theme.Light ? "Light/" : theme == Theme.Black ? "Black/" : theme == Theme.SE98 ? "98SE/" : "Dark/";
var accentDict = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/Themes/Accents/{sub}{accent}.xaml")
Source = new Uri("pack://application:,,,/Themes/MMD.xaml")
};
var target = merged[0];
foreach (object key in accentDict.Keys)
+1 -1
View File
@@ -1,4 +1,4 @@
namespace KillerPDF.Services
namespace MmdPdf.Services
{
/// <summary>
/// Separates fast in-page wheel scrolling from page navigation at the edge. Momentum events