1
0
Fork 0

feat: Everything2Everything 양방향 변환 매트릭스 피보팅

EverythingToJpeg(N→JPEG 단방향)에서 Everything2Everything(N×M 매트릭스)으로
앱을 피보팅. 7개 Provider, 200+ 변환 쌍 지원.

리네이밍
- 디렉토리/솔루션/csproj/namespace/AssemblyName/MSIX Identity 일괄 치환
- 임시파일 prefix e2j_ → e2e_, 로고 라벨 E2J → E2E

아키텍처
- ConversionPair(input, output) 신규 + ProviderCapability.SupportedConversions
- IConverterProvider.ConvertAsync에 outputExtension 파라미터 추가
- ProviderRegistry를 Dictionary<(input, output), Provider> 매트릭스로 재작성
  + OutputsForInput / OutputsForFile 쿼리 API
- ConversionEngine: 출력 ext 라우팅, 동일 입출력 자동 skip,
  서브폴더 suffix를 출력 ext에서 자동 도출
- ConvertOptions를 형식별 sub-record로 분리
  (Jpeg/Png/Webp/Avif/Tiff/Pdf*/Html*/Ocr)

Provider 매트릭스
- MagickProvider: PNG/JPEG/WebP/AVIF/BMP/TIFF/GIF/PDF 양방향 + RAW/PSD 디코딩
  (단일이미지→1페이지 PDF, GIF/TIFF→다페이지 PDF)
- HeicProvider: HEIC/HEIF → 이미지 7종
- PdfProvider: PDF → 이미지 6종 (PNG임베드 후 ImageMagick 인코딩)
- HtmlProvider: HTML/HTM → 이미지 + PDF (CDP printToPDF)
- DocxProvider/HwpxProvider: PDF + 이미지 7종 (LibreOffice 활용)
- OcrProvider 신규: 이미지/PDF → TXT/DOCX
  (Windows.Media.Ocr + DocumentFormat.OpenXml, PDF는 페이지별 OCR 후 결합)

UI
- 사이드바 TARGET FORMAT을 동적 ComboBox로 (큐 파일 매트릭스의 교집합만 표시)
- 출력 형식별 색상 배지 + Quality 패널 라벨 동적
- OUTPUT DESTINATION hint도 출력 ext 기반 동적

CLI
- 신규 'to <ext> <files...>' verb (예: to png photo.heic doc.docx)
- help 텍스트 양방향 컨셉으로 갱신

셸 통합
- ContextMenuRegistrar 카스케이드로 재설계
  (ExtendedSubCommandsKey + Everything2Everything.SubMenu.<ext>)
- 입력별 매트릭스에 따라 인기 출력 10종(JPEG/PNG/WebP/PDF/TXT/DOCX/AVIF/GIF/TIFF/BMP)
  동적 노출, 마지막에 '변환…' 옵션
- 메뉴 라벨 'JPEG로 변환' → 'Everything2Everything으로 변환'

기타
- Core.csproj TFM net9.0-windows → net9.0-windows10.0.19041.0 (WinRT API용)
- DocumentFormat.OpenXml 3.1.0 NuGet 추가
- README 양방향 매트릭스 기준 전면 개편
- MSIX appxmanifest Description / IExplorerCommand DLL 라벨 갱신

빌드: 0 errors, 0 warnings
This commit is contained in:
Yun Chan 2026-05-07 13:40:08 +09:00
parent 96861e627d
commit 9b7e5f0d0c
63 changed files with 1788 additions and 733 deletions

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{1A2B3C4D-5E6F-7A8B-9C0D-EF1234567890}</ProjectGuid>
<RootNamespace>Everything2EverythingShell</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<PreprocessorDefinitions>Everything2Everything_SHELL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<ControlFlowGuard>Guard</ControlFlowGuard>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<CETCompat>true</CETCompat>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Source.def" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets"
Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>WIL NuGet 패키지가 복원되지 않았습니다. nuget restore를 먼저 실행하세요.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')"
Text="$(ErrorText)" />
</Target>
</Project>

View file

@ -0,0 +1,5 @@
LIBRARY
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE

View file

@ -0,0 +1,188 @@
// Everything2Everything shell extension — IExplorerCommand handlers
// Two verbs:
// - QuickCommandHandler → "Everything2Everything.exe quick "<paths>""
// - DialogCommandHandler → "Everything2Everything.exe dialog "<paths>""
#include "pch.h"
#pragma warning(disable : 4324)
using Microsoft::WRL::ClassicCom;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::InhibitRoOriginateError;
using Microsoft::WRL::Module;
using Microsoft::WRL::ModuleType;
using Microsoft::WRL::RuntimeClass;
using Microsoft::WRL::RuntimeClassFlags;
namespace {
constexpr const wchar_t* kExeFileName = L"Everything2Everything.exe";
std::wstring QuoteForCommandLineArg(const std::wstring& arg) {
const std::wstring quotable_chars(L" \\\"");
if (arg.find_first_of(quotable_chars) == std::wstring::npos) {
return arg;
}
std::wstring out;
out.push_back(L'"');
for (size_t i = 0; i < arg.size(); ++i) {
if (arg[i] == L'\\') {
const size_t start = i;
size_t end = start + 1;
for (; end < arg.size() && arg[end] == L'\\'; ++end) {}
size_t backslash_count = end - start;
if (end == arg.size() || arg[end] == L'"') {
backslash_count *= 2;
}
for (size_t j = 0; j < backslash_count; ++j)
out.push_back(L'\\');
i = end - 1;
}
else if (arg[i] == L'"') {
out.push_back(L'\\');
out.push_back(L'"');
}
else {
out.push_back(arg[i]);
}
}
out.push_back(L'"');
return out;
}
std::filesystem::path ResolveExePath() {
std::filesystem::path module_path{
wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
module_path = module_path.remove_filename();
module_path /= kExeFileName;
return module_path;
}
HRESULT LaunchAppWithItems(const wchar_t* verb, IShellItemArray* items) {
if (!items) return S_OK;
DWORD count = 0;
RETURN_IF_FAILED(items->GetCount(&count));
if (count == 0) return S_OK;
auto exe_path = ResolveExePath();
auto command = wil::str_printf<std::wstring>(LR"-("%s" %s)-",
exe_path.c_str(), verb);
for (DWORD i = 0; i < count; ++i) {
ComPtr<IShellItem> item;
if (FAILED(items->GetItemAt(i, &item))) continue;
wil::unique_cotaskmem_string path;
if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &path))) continue;
command = wil::str_printf<std::wstring>(LR"-(%s %s)-",
command.c_str(),
QuoteForCommandLineArg(path.get()).c_str());
}
wil::unique_process_information process_info;
STARTUPINFOW startup_info = { sizeof(startup_info) };
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
nullptr,
command.data(),
nullptr,
nullptr,
FALSE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
&startup_info,
&process_info));
return S_OK;
}
template <typename Derived>
class CommandHandlerBase : public RuntimeClass<
RuntimeClassFlags<ClassicCom | InhibitRoOriginateError>,
IExplorerCommand>
{
public:
IFACEMETHODIMP GetTitle(IShellItemArray*, PWSTR* name) override {
return SHStrDupW(Derived::Title(), name);
}
IFACEMETHODIMP GetIcon(IShellItemArray*, PWSTR* icon) override {
auto exe = ResolveExePath();
return SHStrDupW(exe.c_str(), icon);
}
IFACEMETHODIMP GetToolTip(IShellItemArray*, PWSTR* infoTip) override {
*infoTip = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP GetCanonicalName(GUID* guidCommandName) override {
*guidCommandName = GUID_NULL;
return S_OK;
}
IFACEMETHODIMP GetState(IShellItemArray*, BOOL, EXPCMDSTATE* cmdState) override {
*cmdState = ECS_ENABLED;
return S_OK;
}
IFACEMETHODIMP GetFlags(EXPCMDFLAGS* flags) override {
*flags = ECF_DEFAULT;
return S_OK;
}
IFACEMETHODIMP EnumSubCommands(IEnumExplorerCommand** enumCommands) override {
*enumCommands = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP Invoke(IShellItemArray* items, IBindCtx*) override {
return LaunchAppWithItems(Derived::Verb(), items);
}
};
} // namespace
class __declspec(uuid("801B2DD3-632C-4731-9510-AEAE09345264"))
QuickCommandHandler final
: public CommandHandlerBase<QuickCommandHandler>
{
public:
static constexpr const wchar_t* Title() { return L"Everything2Everything: 빠른 변환 (JPEG)"; }
static constexpr const wchar_t* Verb() { return L"quick"; }
};
class __declspec(uuid("CEBA1DB7-9175-4DF6-A362-490DEA49B598"))
DialogCommandHandler final
: public CommandHandlerBase<DialogCommandHandler>
{
public:
static constexpr const wchar_t* Title() { return L"Everything2Everything: 변환…"; }
static constexpr const wchar_t* Verb() { return L"dialog"; }
};
CoCreatableClass(QuickCommandHandler)
CoCreatableClass(DialogCommandHandler)
CoCreatableClassWrlCreatorMapInclude(QuickCommandHandler)
CoCreatableClassWrlCreatorMapInclude(DialogCommandHandler)
BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) {
return TRUE;
}
_Check_return_
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
if (ppv == nullptr) return E_POINTER;
*ppv = nullptr;
return Module<ModuleType::InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
}
__control_entrypoint(DllExport)
STDAPI DllCanUnloadNow(void) {
return Module<ModuleType::InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
}

View file

@ -0,0 +1,4 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View file

@ -0,0 +1 @@
#include "pch.h"

View file

@ -0,0 +1,24 @@
#ifndef PCH_H
#define PCH_H
#include "framework.h"
#include <filesystem>
#include <string>
#include <shlobj_core.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include <wrl/client.h>
#include <wrl/implements.h>
#include <wrl/module.h>
#pragma comment(lib, "runtimeobject.lib")
#pragma warning(push)
#pragma warning(disable: 28182)
#include <wil/stl.h>
#include <wil/win32_helpers.h>
#pragma warning(pop)
#endif