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
+64 -212
View File
@@ -16,10 +16,10 @@ using Microsoft.Win32;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.IO;
using KillerPDF.Services;
using MmdPdf.Services;
using PdfPigDoc = UglyToad.PdfPig.PdfDocument;
namespace KillerPDF
namespace MmdPdf
{
public partial class MainWindow
{
@@ -31,48 +31,6 @@ namespace KillerPDF
// Syncs every picker's radios and accent dots to live state before a flyout shows -
// exactly ONE sync implementation, shared by all the rail flyouts (Shell/RailFlyouts.cs).
private void SyncPickerState()
{
var cur = ThemeManager.Current;
ThemeDarkRadio.IsChecked = cur == Theme.Dark;
ThemeLightRadio.IsChecked = cur == Theme.Light;
ThemeHCRadio.IsChecked = cur == Theme.Black;
Theme98SERadio.IsChecked = cur == Theme.SE98;
ThemeBloodRadio.IsChecked = cur == Theme.Blood;
ThemeGreedRadio.IsChecked = cur == Theme.Greed;
ThemeCyanoticRadio.IsChecked = cur == Theme.Cyanotic;
ThemeEctoplasmRadio.IsChecked = cur == Theme.Ectoplasm;
ThemeDecayRadio.IsChecked = cur == Theme.Decay;
ThemeMourningRadio.IsChecked = cur == Theme.Mourning;
ThemeSepulchreRadio.IsChecked = cur == Theme.Sepulchre;
ThemeDeliriumRadio.IsChecked = cur == Theme.Delirium;
ThemeMalaiseRadio.IsChecked = cur == Theme.Malaise;
UpdateAccentStrip(animate: false);
// Sync language picker
var curLoc = KillerPDF.Services.LocaleManager.Current;
LangEnRadio.IsChecked = curLoc == KillerPDF.Services.Locale.EnUS;
LangCsRadio.IsChecked = curLoc == KillerPDF.Services.Locale.CsCZ;
LangEsRadio.IsChecked = curLoc == KillerPDF.Services.Locale.Es;
LangFrRadio.IsChecked = curLoc == KillerPDF.Services.Locale.Fr;
LangZhTWRadio.IsChecked = curLoc == KillerPDF.Services.Locale.ZhTW;
LangZhCNRadio.IsChecked = curLoc == KillerPDF.Services.Locale.ZhCN;
LangBnRadio.IsChecked = curLoc == KillerPDF.Services.Locale.Bn;
LangTrRadio.IsChecked = curLoc == KillerPDF.Services.Locale.TrTR;
LangDeRadio.IsChecked = curLoc == KillerPDF.Services.Locale.De;
LangJaRadio.IsChecked = curLoc == KillerPDF.Services.Locale.JaJP;
LangPlRadio.IsChecked = curLoc == KillerPDF.Services.Locale.PlPL;
LangHuRadio.IsChecked = curLoc == KillerPDF.Services.Locale.HuHU;
// Sync view mode radios. Against the PENDING mode while a fade-wrapped switch is in
// flight (_viewMode lags until the fade-out lands), so wheel-cycling with the flyout
// open moves the checkmark in step instead of one notch behind.
var vm = _pendingViewMode ?? _viewMode;
ViewSingleRadio.IsChecked = vm == ViewMode.Single;
ViewContinuousRadio.IsChecked = vm == ViewMode.Continuous;
ViewTwoPageRadio.IsChecked = vm == ViewMode.TwoPage;
ViewGridRadio.IsChecked = vm == ViewMode.Grid;
// (The toolbar picker lives on the toolbar's own right-click menu, and the sidebar
// side on the sidebar's - both build their checks fresh on open, nothing to sync here.)
}
// ── Quick fade in/out for the full-window overlay panels (Shortcuts/About) ──
private static void FadeOverlayIn(UIElement el)
@@ -340,176 +298,41 @@ namespace KillerPDF
RefreshOpenAnnotationBars();
}
private void ThemeDarkRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Dark);
private void ThemeLightRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Light);
private void ThemeHCRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Black);
private void Theme98SERadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.SE98);
private void ThemeBloodRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Blood);
private void ThemeGreedRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Greed);
private void ThemeCyanoticRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Cyanotic);
private void ThemeEctoplasmRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Ectoplasm);
private void ThemeDecayRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Decay);
private void ThemeMourningRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Mourning);
private void ThemeSepulchreRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Sepulchre);
private void ThemeDeliriumRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Delirium);
private void ThemeMalaiseRadio_Checked(object sender, RoutedEventArgs e) => SelectTheme(Theme.Malaise);
private void SelectTheme(Theme theme)
{
bool wasOpen = ThemeFlyout is not null && ThemeFlyout.IsOpen;
ThemeManager.Apply(theme);
UpdateAccentStrip(animate: true);
// Intentionally leave the flyout open so the user can try another theme right away
// without reopening the submenu. The theme swap's side effects (tab strip rebuild,
// tool re-select) can still knock the popup closed behind our back, so check once
// layout has settled and quietly reopen it in place if that happened.
if (wasOpen)
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,
(Action)(() => { if (ThemeFlyout is not null && !ThemeFlyout.IsOpen) ThemeFlyout.IsOpen = true; }));
}
// #199 (Ryokoxx's design): ONE vertical strip of six swatches beside the theme list,
// repainted per family, instead of a row under each accented theme. The list height is
// identical for every theme, so the bottom-pinned card never moves; only the strip's
// width animates, growing rightward from the pinned left edge. Per-family accent memory
// in ThemeManager is untouched.
private void AccentStripDot_Click(object sender, MouseButtonEventArgs e) => HandleAccentDot(sender, _stripFamily);
private void HandleAccentDot(object sender, Theme family)
{
if (sender is not FrameworkElement fe || fe.Tag is not string tag) return;
if (!Enum.TryParse<DarkAccent>(tag, out var accent)) return;
ThemeManager.ApplyAccent(family, accent); // persists for that family; reapplies if active
RingAccentStrip();
}
// Per-family swatch colors, in each family's display order (moved here from the four
// retired XAML accent rows; the strip repaints these onto its six shared dots).
private static readonly (DarkAccent Accent, string Hex)[] DarkStripColors =
[(DarkAccent.Red, "#DD504B"), (DarkAccent.Orange, "#E8962C"), (DarkAccent.Green, "#1EA54C"),
(DarkAccent.Teal, "#1FB8A8"), (DarkAccent.Blue, "#4580D9"), (DarkAccent.Purple, "#B982E3")];
private static readonly (DarkAccent Accent, string Hex)[] LightStripColors =
[(DarkAccent.Red, "#931A1A"), (DarkAccent.Orange, "#C7710F"), (DarkAccent.Green, "#1B5E20"),
(DarkAccent.Teal, "#0D827E"), (DarkAccent.Blue, "#18608E"), (DarkAccent.Purple, "#5A1690")];
private static readonly (DarkAccent Accent, string Hex)[] BlackStripColors =
[(DarkAccent.Red, "#FF2929"), (DarkAccent.Orange, "#FF910A"), (DarkAccent.Green, "#00FF66"),
(DarkAccent.Teal, "#0AFFE7"), (DarkAccent.Blue, "#298DFF"), (DarkAccent.Purple, "#B829FF")];
private static readonly (DarkAccent Accent, string Hex)[] SE98StripColors =
[(DarkAccent.Red, "#800040"), (DarkAccent.Orange, "#A05000"), (DarkAccent.Green, "#006000"),
(DarkAccent.Teal, "#008080"), (DarkAccent.Blue, "#000080"), (DarkAccent.Purple, "#5A376E")];
private static readonly (DarkAccent Accent, string Hex)[] DarkStripColors = [];
private static readonly (DarkAccent Accent, string Hex)[] LightStripColors = [];
private static readonly (DarkAccent Accent, string Hex)[] BlackStripColors = [];
private static readonly (DarkAccent Accent, string Hex)[] SE98StripColors = [];
private static (DarkAccent Accent, string Hex)[] StripColorsFor(Theme family) => family switch
{
Theme.Light => LightStripColors,
Theme.Black => BlackStripColors,
Theme.SE98 => SE98StripColors,
_ => DarkStripColors,
};
private Theme _stripFamily = Theme.Dark; // the family the strip's dots currently show
private bool _stripOpen;
private const double AccentStripWidth = 39; // 1px rule + 10 gap + 26 swatch + 2 air
private const double AccentStripSlideMs = 180;
private Border[] StripDots =>
[AccentStripDot0, AccentStripDot1, AccentStripDot2, AccentStripDot3, AccentStripDot4, AccentStripDot5];
private void PopulateAccentStrip(Theme family)
{
var colors = StripColorsFor(family);
var dots = StripDots;
for (int i = 0; i < dots.Length; i++)
{
var c = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(colors[i].Hex);
dots[i].Background = new System.Windows.Media.SolidColorBrush(c);
dots[i].Tag = colors[i].Accent.ToString();
}
_stripFamily = family;
RingAccentStrip();
}
private Border[] StripDots => [];
// Ring the strip's selected swatch for the family it is showing.
private void RingAccentStrip()
{
if (AccentStrip is null) return;
var ring = (System.Windows.Media.Brush)FindResource("TextBrush");
var chosen = _stripFamily switch
{
Theme.Light => ThemeManager.LightAccentChoice,
Theme.Black => ThemeManager.BlackAccentChoice,
Theme.SE98 => ThemeManager.SE98AccentChoice,
_ => ThemeManager.DarkAccentChoice,
};
foreach (var dot in StripDots)
{
bool sel = dot.Tag is string t && Enum.TryParse<DarkAccent>(t, out var a) && a == chosen;
dot.BorderBrush = sel ? ring : System.Windows.Media.Brushes.Transparent;
}
}
private void UpdateAccentStrip(bool animate)
{
var cur = ThemeManager.Current;
bool show = cur is Theme.Dark or Theme.Light or Theme.Black or Theme.SE98;
if (show)
{
if (animate && _stripOpen && _stripFamily != cur)
{
// Accented -> accented: the strip stays open and the swatches CROSSFADE.
// Re-running the open animation here snapped the strip shut and back.
var target = cur;
var fadeOut = new System.Windows.Media.Animation.DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(90));
fadeOut.Completed += (_, __) =>
{
PopulateAccentStrip(target);
AccentStrip.BeginAnimation(OpacityProperty,
new System.Windows.Media.Animation.DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(90)));
};
AccentStrip.BeginAnimation(OpacityProperty, fadeOut);
}
else PopulateAccentStrip(cur);
}
SlideAccentStrip(show, animate);
}
// Width slide, eased - one element animates now, so the old linear lockstep constraint
// that kept two rows summing to a constant height no longer applies.
private void SlideAccentStrip(bool show, bool animate)
{
if (show == _stripOpen && animate) return; // already there; crossfade handles content
_stripOpen = show;
AccentStripHost.BeginAnimation(WidthProperty, null); // drop any in-flight slide
if (!animate) { AccentStripHost.Width = show ? AccentStripWidth : 0; return; }
double from = double.IsNaN(AccentStripHost.Width) ? AccentStripHost.ActualWidth : AccentStripHost.Width;
var anim = new System.Windows.Media.Animation.DoubleAnimation(from, show ? AccentStripWidth : 0,
TimeSpan.FromMilliseconds(AccentStripSlideMs))
{
EasingFunction = new System.Windows.Media.Animation.QuadraticEase
{ EasingMode = System.Windows.Media.Animation.EasingMode.EaseOut }
};
anim.Completed += (_, __) =>
{
AccentStripHost.BeginAnimation(WidthProperty, null);
AccentStripHost.Width = _stripOpen ? AccentStripWidth : 0;
};
AccentStripHost.BeginAnimation(WidthProperty, anim);
}
// Localized display name for each theme, shown on the picker row.
private string ThemeDisplayName(Theme t) => t switch
{
Theme.Light => Loc("Str_Theme_Light"),
Theme.Black => Loc("Str_Theme_Black"),
Theme.SE98 => Loc("Str_Theme_98SE"),
Theme.Blood => Loc("Str_Theme_Blood"),
Theme.Greed => Loc("Str_Theme_Greed"),
Theme.Cyanotic => Loc("Str_Theme_Cyanotic"),
Theme.Ectoplasm => Loc("Str_Theme_Ectoplasm"),
Theme.Decay => Loc("Str_Theme_Decay"),
Theme.Mourning => Loc("Str_Theme_Mourning"),
Theme.Sepulchre => Loc("Str_Theme_Sepulchre"),
Theme.Delirium => Loc("Str_Theme_Delirium"),
Theme.Malaise => Loc("Str_Theme_Malaise"),
_ => Loc("Str_Theme_Dark"),
};
@@ -518,7 +341,7 @@ namespace KillerPDF
// DynamicResource strings. Called once from the window ctor; the event only ever fires
// when the app was started with --lang-file.
internal void HookExternalLangReload()
=> KillerPDF.Services.LocaleManager.ExternalReloaded += () =>
=> MmdPdf.Services.LocaleManager.ExternalReloaded += () =>
{
ApplyToolNumberTooltips();
BuildToolbarMenu();
@@ -527,22 +350,22 @@ namespace KillerPDF
RefreshOpenAnnotationBars();
};
private void LangEnRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.EnUS);
private void LangCsRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.CsCZ);
private void LangEsRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.Es);
private void LangFrRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.Fr);
private void LangZhTWRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.ZhTW);
private void LangZhCNRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.ZhCN);
private void LangBnRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.Bn);
private void LangTrRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.TrTR);
private void LangDeRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.De);
private void LangJaRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.JaJP);
private void LangPlRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.PlPL);
private void LangHuRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(KillerPDF.Services.Locale.HuHU);
private void LangEnRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.EnUS);
private void LangCsRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.CsCZ);
private void LangEsRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.Es);
private void LangFrRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.Fr);
private void LangZhTWRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.ZhTW);
private void LangZhCNRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.ZhCN);
private void LangBnRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.Bn);
private void LangTrRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.TrTR);
private void LangDeRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.De);
private void LangJaRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.JaJP);
private void LangPlRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.PlPL);
private void LangHuRadio_Checked(object sender, RoutedEventArgs e) => SelectLocale(MmdPdf.Services.Locale.HuHU);
private void SelectLocale(KillerPDF.Services.Locale loc)
private void SelectLocale(MmdPdf.Services.Locale loc)
{
KillerPDF.Services.LocaleManager.Apply(loc);
MmdPdf.Services.LocaleManager.Apply(loc);
ApplyToolNumberTooltips(); // re-append the numbers to the now-localized tool tooltips
BuildToolbarMenu(); // the toolbar right-click picker's items carry Loc() captions
LangFlyout.IsOpen = false; // a pick closes the rail flyout, like the accordion used to collapse
@@ -592,9 +415,9 @@ namespace KillerPDF
RefreshOpenAnnotationBars(active);
}
private void RefreshOpenAnnotationBars(KillerPDF.Controls.PdfViewer viewer)
private void RefreshOpenAnnotationBars(MmdPdf.Controls.PdfViewer viewer)
{
((KillerPDF.Features.IViewerHost)this).RunWithViewerContext(viewer, () =>
((MmdPdf.Features.IViewerHost)this).RunWithViewerContext(viewer, () =>
{
var tool = _annotBarTool;
if (tool == EditTool.Text)
@@ -638,17 +461,17 @@ namespace KillerPDF
}
// Native name (autonym) for each language, shown in the picker regardless of UI locale.
private static string LangDisplayName(KillerPDF.Services.Locale loc) => loc switch
private static string LangDisplayName(MmdPdf.Services.Locale loc) => loc switch
{
KillerPDF.Services.Locale.CsCZ => "Čeština",
KillerPDF.Services.Locale.Es => "Español",
KillerPDF.Services.Locale.Fr => "Français",
KillerPDF.Services.Locale.ZhTW => "中文 (繁體)",
KillerPDF.Services.Locale.ZhCN => "中文 (简体)",
KillerPDF.Services.Locale.Bn => "বাংলা",
KillerPDF.Services.Locale.TrTR => "Türkçe",
KillerPDF.Services.Locale.De => "Deutsch",
KillerPDF.Services.Locale.JaJP => "日本語",
MmdPdf.Services.Locale.CsCZ => "Čeština",
MmdPdf.Services.Locale.Es => "Español",
MmdPdf.Services.Locale.Fr => "Français",
MmdPdf.Services.Locale.ZhTW => "中文 (繁體)",
MmdPdf.Services.Locale.ZhCN => "中文 (简体)",
MmdPdf.Services.Locale.Bn => "বাংলা",
MmdPdf.Services.Locale.TrTR => "Türkçe",
MmdPdf.Services.Locale.De => "Deutsch",
MmdPdf.Services.Locale.JaJP => "日本語",
_ => "English",
};
@@ -670,7 +493,7 @@ namespace KillerPDF
// ── Toolbar appearance (right-click picker on the bar) ────────────
// Hover tooltips stay on in every mode, so the text modes are about preference, not
// discoverability.
// TWO AXES, NOT ONE (family standard, 2026-07-30; KillerUI/Shell/ToolbarStyle.cs is
// TWO AXES, NOT ONE (family standard, 2026-07-30; MmdPdfUI/Shell/ToolbarStyle.cs is
// the reference). The old five-way ToolbarStyle could not express "large icons WITH text" -
// icon size and text placement were never one axis, they only looked like one. The old
// enum survives solely so an existing install's saved setting migrates (InitToolbarStyle).
@@ -1192,5 +1015,34 @@ namespace KillerPDF
{
OverflowChevron.IsChecked = false; // close the flyout after a choice is made
}
private void SyncPickerState()
{
var cur = ThemeManager.Current;
// Sync language picker
var curLoc = MmdPdf.Services.LocaleManager.Current;
LangEnRadio.IsChecked = curLoc == MmdPdf.Services.Locale.EnUS;
LangCsRadio.IsChecked = curLoc == MmdPdf.Services.Locale.CsCZ;
LangEsRadio.IsChecked = curLoc == MmdPdf.Services.Locale.Es;
LangFrRadio.IsChecked = curLoc == MmdPdf.Services.Locale.Fr;
LangZhTWRadio.IsChecked = curLoc == MmdPdf.Services.Locale.ZhTW;
LangZhCNRadio.IsChecked = curLoc == MmdPdf.Services.Locale.ZhCN;
LangBnRadio.IsChecked = curLoc == MmdPdf.Services.Locale.Bn;
LangTrRadio.IsChecked = curLoc == MmdPdf.Services.Locale.TrTR;
LangDeRadio.IsChecked = curLoc == MmdPdf.Services.Locale.De;
LangJaRadio.IsChecked = curLoc == MmdPdf.Services.Locale.JaJP;
LangPlRadio.IsChecked = curLoc == MmdPdf.Services.Locale.PlPL;
LangHuRadio.IsChecked = curLoc == MmdPdf.Services.Locale.HuHU;
// Sync view mode radios. Against the PENDING mode while a fade-wrapped switch is in
// flight (_viewMode lags until the fade-out lands), so wheel-cycling with the flyout
// open moves the checkmark in step instead of one notch behind.
var vm = _pendingViewMode ?? _viewMode;
ViewSingleRadio.IsChecked = vm == ViewMode.Single;
ViewContinuousRadio.IsChecked = vm == ViewMode.Continuous;
ViewTwoPageRadio.IsChecked = vm == ViewMode.TwoPage;
ViewGridRadio.IsChecked = vm == ViewMode.Grid;
// (The toolbar picker lives on the toolbar's own right-click menu, and the sidebar
// side on the sidebar's - both build their checks fresh on open, nothing to sync here.)
}
}
}