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:
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
namespace KillerPDF.Services
|
||||
namespace MmdPdf.Services
|
||||
{
|
||||
// ============================================================
|
||||
// Minimal 'cmap' reader: answers "does this face have a glyph for this
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
namespace KillerPDF.Services
|
||||
namespace MmdPdf.Services
|
||||
{
|
||||
// ============================================================
|
||||
// Glyph coverage + the fallback chain (#168).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
using PdfPigDoc = UglyToad.PdfPig.PdfDocument;
|
||||
|
||||
namespace KillerPDF.Services
|
||||
namespace MmdPdf.Services
|
||||
{
|
||||
// ============================================================
|
||||
// Image placement extraction for the display dark mode (#135
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
using UglyToad.PdfPig;
|
||||
|
||||
namespace KillerPDF.Services
|
||||
namespace MmdPdf.Services
|
||||
{
|
||||
internal sealed class SearchResult
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
// ============================================================
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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,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
@@ -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,4 +1,4 @@
|
||||
namespace KillerPDF.Services
|
||||
namespace MmdPdf.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Separates fast in-page wheel scrolling from page navigation at the edge. Momentum events
|
||||
|
||||
Reference in New Issue
Block a user