From ea9872cae8f0e8e04d4b75e9186200ecb5943e6d Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Wed, 4 Jan 2023 10:01:01 +0900 Subject: [PATCH] first commit --- .gitignore | 18 + .gitmodules | 6 + CMakeLists.txt | 62 ++ HISTORY.txt | 1 + Jigsaw.cpp | 1312 +++++++++++++++++++++++++++++++++++++++ Jigsaw_res.rc | 110 ++++ LICENSE.txt | 27 + README.md | 33 + README.txt | 33 + SHA-256.cpp | 259 ++++++++ SHA-256.hpp | 175 ++++++ Shareware.cpp | 1027 ++++++++++++++++++++++++++++++ Shareware.hpp | 131 ++++ Shareware.rc | 53 ++ Shareware_inl.hpp | 61 ++ Susie.hpp | 329 ++++++++++ TempFile.hpp | 115 ++++ color_value | 1 + do_build.bat | 4 + fontmap.dat | 43 ++ gpimage.cpp | 469 ++++++++++++++ gpimage.hpp | 21 + hand.cur | Bin 0 -> 326 bytes installer.iss | 69 ++ libharu | 1 + res/Manifest_1.manifest | 15 + resource.h | 15 + 27 files changed, 4390 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 HISTORY.txt create mode 100644 Jigsaw.cpp create mode 100644 Jigsaw_res.rc create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 README.txt create mode 100644 SHA-256.cpp create mode 100644 SHA-256.hpp create mode 100644 Shareware.cpp create mode 100644 Shareware.hpp create mode 100644 Shareware.rc create mode 100644 Shareware_inl.hpp create mode 100644 Susie.hpp create mode 100644 TempFile.hpp create mode 160000 color_value create mode 100644 do_build.bat create mode 100644 fontmap.dat create mode 100644 gpimage.cpp create mode 100644 gpimage.hpp create mode 100644 hand.cur create mode 100644 installer.iss create mode 160000 libharu create mode 100644 res/Manifest_1.manifest create mode 100644 resource.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..968732a --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +__pycache__ +build +dist +*.spec +*.exe +*.o +*.cmake +CMakeCache.txt +CMakeFiles +Makefile +a.* +*.ninja +*.ninja* +*-old +lib*.a +*.dll +*.pdf +*.spi diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..54866c9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libharu"] + path = libharu + url = https://github.com/katahiromz/libharu +[submodule "color_value"] + path = color_value + url = https://github.com/katahiromz/color_value diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4c67e6b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,62 @@ +# CMakeLists.txt --- CMake project settings +# ex) cmake -G "Visual Studio 9 2008" . +# ex) cmake -DCMAKE_BUILD_TYPE=Release -G "MSYS Makefiles" . +############################################################################## + +# CMake minimum version +cmake_minimum_required(VERSION 3.0) + +# project name and languages +project(Jigsaw CXX RC) + +# set output directory (build/) +set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build) +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) + +# statically link +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # using Clang + set(CMAKE_CXX_FLAGS "-static -lstdc++ -lgcc") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # using GCC + set(CMAKE_CXX_FLAGS "-static -lstdc++ -lgcc") +elseif (MSVC) + # replace "/MD" with "/MT" (building without runtime DLLs) + set(CompilerFlags + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO) + foreach(CompilerFlags ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlags} "${${CompilerFlags}}") + endforeach() +endif() + +############################################################################## + +# Shareware? +option(SHAREWARE "Enable shareware" ON) +if(NOT SHAREWARE) + add_definitions(-DNO_SHAREWARE) +endif() + +# Unicode +add_definitions(-DUNICODE -D_UNICODE) + +# zlib +find_package(ZLIB) + +# libpng +find_package(PNG) + +# libharu (hpdf) +set(LIBHPDF_STATIC ON) +add_subdirectory(libharu) +include_directories(libharu/include) + +# Jigsaw.exe +add_executable(Jigsaw WIN32 Jigsaw.cpp Jigsaw_res.rc gpimage.cpp Shareware.cpp SHA-256.cpp) +target_link_libraries(Jigsaw comctl32 shlwapi gdiplus hpdf ${PNG_LIBRARY} ${ZLIB_LIBRARY}) + +############################################################################## diff --git a/HISTORY.txt b/HISTORY.txt new file mode 100644 index 0000000..92883c9 --- /dev/null +++ b/HISTORY.txt @@ -0,0 +1 @@ +# デジタルジグソーメーカーの開発履歴 diff --git a/Jigsaw.cpp b/Jigsaw.cpp new file mode 100644 index 0000000..8bdf902 --- /dev/null +++ b/Jigsaw.cpp @@ -0,0 +1,1312 @@ +// デジタルジグソーメーカー by katahiromz +// Copyright (C) 2023 片山博文MZ. All Rights Reserved. +// See README.txt and LICENSE.txt. +#include // Windowsの標準ヘッダ。 +#include // Windowsのマクロヘッダ。 +#include // 共通コントロールのヘッダ。 +#include // 共通ダイアログのヘッダ。 +#include // シェルAPIのヘッダ。 +#include // シェル軽量APIのヘッダ。 +#include // ジェネリックテキストマッピング用のヘッダ。 +#include // 安全な文字列操作用のヘッダ (StringC*) +#include // std::string および std::wstring クラス。 +#include // std::vector クラス。 +#include // std::map クラス。 +#include // std::runtime_error クラス。 +#include // assertマクロ。 +#include // PDF出力用のライブラリlibharuのヘッダ。 +#include "TempFile.hpp" // 一時ファイル操作用のヘッダ。 +#include "gpimage.hpp" // GDI+を用いた画像ファイル入出力ライブラリ。 +#include "Susie.hpp" // Susieプラグインサポート。 +#include "resource.h" // リソースIDの定義ヘッダ。 + +// シェアウェア情報。 +#ifndef NO_SHAREWARE + #include "Shareware.hpp" + + SW_Shareware g_shareware( + /* company registry key */ TEXT("Katayama Hirofumi MZ"), + /* application registry key */ TEXT("DigitalJigsawMaker"), + /* password hash */ + "e218f83f070a186f886c6dc82bd7ecf3d6c3ea4224fd7d213aa06e9c9713b395", + /* trial days */ 10, + /* salt string */ "mJpDxx2D", + /* version string */ "0.0.0"); +#endif + +#define UTF8_SUPPORT // UTF-8サポート。 + +// 文字列クラス。 +#ifdef UNICODE + typedef std::wstring string_t; +#else + typedef std::string string_t; +#endif + +// シフトJIS コードページ(Shift_JIS)。 +#define CP932 932 + +// わかりやすい項目名を使用する。 +enum +{ + IDC_GENERATE = IDOK, + IDC_EXIT = IDCANCEL, +}; + +// Susieプラグイン マネジャー。 +SusiePluginManager g_susie; + +struct FONT_ENTRY +{ + string_t m_font_name; + string_t m_pathname; + int m_index = -1; +}; + +// ガゾーナラベPDFのメインクラス。 +class JigsawMaker +{ +public: + HINSTANCE m_hInstance; + INT m_argc; + LPTSTR *m_argv; + HBITMAP m_hbmPreview; + std::map m_settings; + std::vector m_list; + std::vector m_font_map; + + // コンストラクタ。 + JigsawMaker(HINSTANCE hInstance, INT argc, LPTSTR *argv); + + // デストラクタ。 + ~JigsawMaker() + { + ::DeleteObject(m_hbmPreview); + } + + // フォントマップを読み込む。 + BOOL LoadFontMap(); + // データをリセットする。 + void Reset(); + // ダイアログを初期化する。 + void InitDialog(HWND hwnd); + // ダイアログからデータへ。 + BOOL DataFromDialog(HWND hwnd, BOOL bList); + // データからダイアログへ。 + BOOL DialogFromData(HWND hwnd, BOOL bList); + // レジストリからデータへ。 + BOOL DataFromReg(HWND hwnd); + // データからレジストリへ。 + BOOL RegFromData(HWND hwnd); + // タグを置き換える。 + bool SubstituteTags(HWND hwnd, string_t& str, const string_t& pathname, + INT iImage, INT cImages, INT iPage, INT cPages, bool is_output); + + // メインディッシュ処理。 + string_t JustDoIt(HWND hwnd); +}; + +// グローバル変数。 +HINSTANCE g_hInstance = NULL; // インスタンス。 +TCHAR g_szAppName[256] = TEXT(""); // アプリ名。 +HICON g_hIcon = NULL; // アイコン(大)。 +HICON g_hIconSm = NULL; // アイコン(小)。 + +// リソース文字列を読み込む。 +LPTSTR doLoadString(INT nID) +{ + static TCHAR s_szText[1024]; + s_szText[0] = 0; + LoadString(NULL, nID, s_szText, _countof(s_szText)); + return s_szText; +} + +// 文字列の前後の空白を削除する。 +void str_trim(LPWSTR text) +{ + StrTrimW(text, L" \t\r\n\x3000"); +} + +// ローカルのファイルのパス名を取得する。 +LPCTSTR findLocalFile(LPCTSTR filename) +{ + // 現在のプログラムのパスファイル名を取得する。 + static TCHAR szPath[MAX_PATH]; + GetModuleFileName(NULL, szPath, _countof(szPath)); + + // ファイルタイトルをfilenameで置き換える。 + PathRemoveFileSpec(szPath); + PathAppend(szPath, filename); + if (PathFileExists(szPath)) + return szPath; + + // 一つ上のフォルダへ。 + PathRemoveFileSpec(szPath); + PathRemoveFileSpec(szPath); + PathAppend(szPath, filename); + if (PathFileExists(szPath)) + return szPath; + + // さらに一つ上のフォルダへ。 + PathRemoveFileSpec(szPath); + PathRemoveFileSpec(szPath); + PathRemoveFileSpec(szPath); + PathAppend(szPath, filename); + if (PathFileExists(szPath)) + return szPath; + + return NULL; // 見つからなかった。 +} + +// 不正な文字列が入力された。 +void OnInvalidString(HWND hwnd, INT nItemID, INT nFieldId, INT nReasonId) +{ + SetFocus(GetDlgItem(hwnd, nItemID)); + string_t field = doLoadString(nFieldId); + string_t reason = doLoadString(nReasonId); + TCHAR szText[256]; + StringCchPrintf(szText, _countof(szText), doLoadString(IDS_INVALIDSTRING), field.c_str(), reason.c_str()); + MessageBox(hwnd, szText, g_szAppName, MB_ICONERROR); +} + +// コンボボックスのテキストを取得する。 +BOOL getComboText(HWND hwnd, INT id, LPTSTR text, INT cchMax) +{ + text[0] = 0; + + HWND hCombo = GetDlgItem(hwnd, id); + INT iSel = ComboBox_GetCurSel(hCombo); + if (iSel == CB_ERR) // コンボボックスに選択項目がなければ + { + // そのままテキストを取得する。 + ComboBox_GetText(hCombo, text, cchMax); + } + else + { + // リストからテキストを取得する。長さのチェックあり。 + if (ComboBox_GetLBTextLen(hCombo, iSel) >= cchMax) + { + StringCchCopy(text, cchMax, doLoadString(IDS_TEXTTOOLONG)); + return FALSE; + } + else + { + ComboBox_GetLBText(hCombo, iSel, text); + } + } + + return TRUE; +} + +// コンボボックスのテキストを設定する。 +BOOL setComboText(HWND hwnd, INT id, LPCTSTR text) +{ + // テキストに一致する項目を取得する。 + HWND hCombo = GetDlgItem(hwnd, id); + INT iItem = ComboBox_FindStringExact(hCombo, -1, text); + if (iItem == CB_ERR) // 一致する項目がなければ + ComboBox_SetText(hCombo, text); // そのままテキストを設定。 + else + ComboBox_SetCurSel(hCombo, iItem); // 一致する項目を選択。 + return TRUE; +} + +// ワイド文字列をANSI文字列に変換する。 +LPSTR ansi_from_wide(UINT codepage, LPCWSTR wide) +{ + static CHAR s_ansi[1024]; + + // コードページで表示できない文字はゲタ文字(〓)にする。 + static const char utf8_geta[] = "\xE3\x80\x93"; + static const char cp932_geta[] = "\x81\xAC"; + const char *geta = NULL; + if (codepage == CP_ACP || codepage == CP932) + { + geta = cp932_geta; + } + else if (codepage == CP_UTF8) + { + geta = utf8_geta; + } + + WideCharToMultiByte(codepage, 0, wide, -1, s_ansi, _countof(s_ansi), geta, NULL); + return s_ansi; +} + +// ANSI文字列をワイド文字列に変換する。 +LPWSTR wide_from_ansi(UINT codepage, LPCSTR ansi) +{ + static WCHAR s_wide[1024]; + MultiByteToWideChar(codepage, 0, ansi, -1, s_wide, _countof(s_wide)); + return s_wide; +} + +// mm単位からピクセル単位への変換。 +double pixels_from_mm(double mm, double dpi = 72) +{ + return dpi * mm / 25.4; +} + +// ピクセル単位からmm単位への変換。 +double mm_from_pixels(double pixels, double dpi = 72) +{ + return 25.4 * pixels / dpi; +} + +// ファイル名に使えない文字を下線文字に置き換える。 +void validate_filename(string_t& filename) +{ + for (auto& ch : filename) + { + if (wcschr(L"\\/:*?\"<>|", ch) != NULL) + ch = L'_'; + } +} + +// 有効な画像ファイルかを確認する。 +bool isValidImageFile(LPCTSTR filename) +{ + // 有効なファイルか? + if (!PathFileExists(filename) || PathIsDirectory(filename)) + return false; + + // GDI+などで読み込める画像ファイルか? + if (gpimage_is_valid_extension(filename)) + return true; + + // Susie プラグインで読み込める画像ファイルか? + if (g_susie.is_loaded()) + { + auto ansi = ansi_from_wide(CP_ACP, filename); + auto dotext = PathFindExtensionA(ansi); + if (g_susie.is_dotext_supported(dotext)) + return true; + } + + return false; // 読み込めない。 +} + +// 画像を読み込む。 +HBITMAP +doLoadPic(LPCWSTR filename, int* width = NULL, int* height = NULL, + FILETIME* pftCreated = NULL, FILETIME* pftModified = NULL) +{ + // GDI+などで読み込みを試みる。 + HBITMAP hbm = gpimage_load(filename, width, height, pftCreated, pftModified); + if (hbm) + return hbm; + + // Susieプラグインを試す。SusieではANSI文字列を使用。 + auto ansi = ansi_from_wide(CP_ACP, filename); + hbm = g_susie.load_image(ansi); + if (hbm == NULL) + { + // ANSI文字列では表現できないパスファイル名かもしれない。 + // 一時ファイルを使用して再度挑戦。 + LPCWSTR dotext = PathFindExtension(filename); + TempFile temp_file; + temp_file.init(L"GN2", dotext); + if (CopyFile(filename, temp_file.make(), FALSE)) + { + ansi = ansi_from_wide(CP_ACP, temp_file.get()); + hbm = g_susie.load_image(ansi); + } + } + if (hbm) + { + // 必要な情報を取得する。 + BITMAP bm; + GetObject(hbm, sizeof(bm), &bm); + if (width) + *width = bm.bmWidth; + if (height) + *height = bm.bmHeight; + gpimage_load_datetime(filename, pftCreated, pftModified); + return hbm; + } + + return NULL; // 失敗。 +} + +// フォントマップを読み込む。 +BOOL JigsawMaker::LoadFontMap() +{ + // 初期化する。 + m_font_map.clear(); + + // ローカルファイルの「fontmap.dat」を探す。 + auto filename = findLocalFile(TEXT("fontmap.dat")); + if (filename == NULL) + return FALSE; // 見つからなかった。 + + // ファイル「fontmap.dat」を開く。 + if (FILE *fp = _tfopen(filename, TEXT("rb"))) + { + // 一行ずつ読み込む。 + char buf[512]; + while (fgets(buf, _countof(buf), fp)) + { + // UTF-8文字列をワイド文字列に変換する。 + WCHAR szText[512]; + MultiByteToWideChar(CP_UTF8, 0, buf, -1, szText, _countof(szText)); + + // 前後の空白を取り除く。 + str_trim(szText); + + // 行コメントを削除する。 + if (auto pch = wcschr(szText, L';')) + { + *pch = 0; + } + + // もう一度前後の空白を取り除く。 + str_trim(szText); + + // 「=」を探す。 + if (auto pch = wcschr(szText, L'=')) + { + // 文字列を切り分ける。 + *pch++ = 0; + auto font_name = szText; + auto font_file = pch; + + // 前後の空白を取り除く。 + str_trim(font_name); + str_trim(font_file); + + // 「,」があればインデックスを読み込み、切り分ける。 + pch = wcschr(pch, L','); + int index = -1; + if (pch) + { + *pch++ = 0; + index = _wtoi(pch); + } + + // さらに前後の空白を取り除く。 + str_trim(font_name); + str_trim(font_file); + + // フォントファイルのパスファイル名を構築する。 + TCHAR font_pathname[MAX_PATH]; + GetWindowsDirectory(font_pathname, _countof(font_pathname)); + PathAppend(font_pathname, TEXT("Fonts")); + PathAppend(font_pathname, font_file); + + // パスファイル名が存在するか? + if (PathFileExists(font_pathname)) + { + // 存在すれば、フォントのエントリーを追加。 + FONT_ENTRY entry; + entry.m_font_name = font_name; + entry.m_pathname = font_pathname; + entry.m_index = index; + m_font_map.push_back(entry); + } + } + } + + // ファイルを閉じる。 + fclose(fp); + } + + return m_font_map.size() > 0; +} + +// コンストラクタ。 +JigsawMaker::JigsawMaker(HINSTANCE hInstance, INT argc, LPTSTR *argv) + : m_hInstance(hInstance) + , m_argc(argc) + , m_argv(argv) + , m_hbmPreview(NULL) +{ + // データをリセットする。 + Reset(); + + // フォントマップを読み込む。 + LoadFontMap(); + + // コマンドライン引数をリストに追加。 + for (INT i = 1; i < m_argc; ++i) + { + // 有効な画像ファイルかを確認して追加。 + if (isValidImageFile(m_argv[i])) + m_list.push_back(m_argv[i]); + } +} + +// 既定値。 +#define IDC_PAGE_SIZE_DEFAULT doLoadString(IDS_A4) +#define IDC_PAGE_DIRECTION_DEFAULT doLoadString(IDS_LANDSCAPE) + +// データをリセットする。 +void JigsawMaker::Reset() +{ +#define SETTING(id) m_settings[TEXT(#id)] + SETTING(IDC_PAGE_SIZE) = IDC_PAGE_SIZE_DEFAULT; + SETTING(IDC_PAGE_DIRECTION) = IDC_PAGE_DIRECTION_DEFAULT; +} + +// ダイアログを初期化する。 +void JigsawMaker::InitDialog(HWND hwnd) +{ + // IDC_PAGE_SIZE: 用紙サイズ。 + SendDlgItemMessage(hwnd, IDC_PAGE_SIZE, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_A3)); + SendDlgItemMessage(hwnd, IDC_PAGE_SIZE, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_A4)); + SendDlgItemMessage(hwnd, IDC_PAGE_SIZE, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_A5)); + SendDlgItemMessage(hwnd, IDC_PAGE_SIZE, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_B4)); + SendDlgItemMessage(hwnd, IDC_PAGE_SIZE, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_B5)); + + // IDC_PAGE_DIRECTION: ページの向き。 + SendDlgItemMessage(hwnd, IDC_PAGE_DIRECTION, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_PORTRAIT)); + SendDlgItemMessage(hwnd, IDC_PAGE_DIRECTION, CB_ADDSTRING, 0, (LPARAM)doLoadString(IDS_LANDSCAPE)); +} + +// ダイアログからデータへ。 +BOOL JigsawMaker::DataFromDialog(HWND hwnd) +{ + TCHAR szText[MAX_PATH]; + + // コンボボックスからデータを取得する。 +#define GET_COMBO_DATA(id) do { \ + getComboText(hwnd, (id), szText, _countof(szText)); \ + str_trim(szText); \ + m_settings[TEXT(#id)] = szText; \ +} while (0) + GET_COMBO_DATA(IDC_PAGE_SIZE); + GET_COMBO_DATA(IDC_PAGE_DIRECTION); +#undef GET_COMBO_DATA + + // チェックボックスからデータを取得する。 +#define GET_CHECK_DATA(id) do { \ + if (IsDlgButtonChecked(hwnd, id) == BST_CHECKED) \ + m_settings[TEXT(#id)] = doLoadString(IDS_YES); \ + else \ + m_settings[TEXT(#id)] = doLoadString(IDS_NO); \ +} while (0) +#undef GET_CHECK_DATA + + return TRUE; +} + +// データからダイアログへ。 +BOOL JigsawMaker::DialogFromData(HWND hwnd) +{ + // コンボボックスへデータを設定する。 +#define SET_COMBO_DATA(id) \ + setComboText(hwnd, (id), m_settings[TEXT(#id)].c_str()); + SET_COMBO_DATA(IDC_PAGE_SIZE); + SET_COMBO_DATA(IDC_PAGE_DIRECTION); +#undef SET_COMBO_DATA + + // チェックボックスへデータを設定する。 +#define SET_CHECK_DATA(id) do { \ + if (m_settings[TEXT(#id)] == doLoadString(IDS_YES)) \ + CheckDlgButton(hwnd, (id), BST_CHECKED); \ + else \ + CheckDlgButton(hwnd, (id), BST_UNCHECKED); \ +} while (0) +#undef SET_CHECK_DATA + + return TRUE; +} + +// レジストリからデータへ。 +BOOL JigsawMaker::DataFromReg(HWND hwnd) +{ + // ソフト固有のレジストリキーを開く。 + HKEY hKey; + RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Katayama Hirofumi MZ\\DigitalJigsawMaker"), 0, KEY_READ, &hKey); + if (!hKey) + return FALSE; // 開けなかった。 + + // レジストリからデータを取得する。 + TCHAR szText[MAX_PATH]; +#define GET_REG_DATA(id) do { \ + szText[0] = 0; \ + DWORD cbText = sizeof(szText); \ + LONG error = RegQueryValueEx(hKey, TEXT(#id), NULL, NULL, (LPBYTE)szText, &cbText); \ + if (error == ERROR_SUCCESS) { \ + SETTING(id) = szText; \ + } \ +} while(0) + GET_REG_DATA(IDC_PAGE_SIZE); + GET_REG_DATA(IDC_PAGE_DIRECTION); +#undef GET_REG_DATA + + // レジストリキーを閉じる。 + RegCloseKey(hKey); + return TRUE; // 成功。 +} + +// データからレジストリへ。 +BOOL JigsawMaker::RegFromData(HWND hwnd) +{ + HKEY hCompanyKey = NULL, hAppKey = NULL; + + // 会社固有のレジストリキーを作成または開く。 + RegCreateKey(HKEY_CURRENT_USER, TEXT("Software\\Katayama Hirofumi MZ"), &hCompanyKey); + if (hCompanyKey == NULL) + return FALSE; // 失敗。 + + // ソフト固有のレジストリキーを作成または開く。 + RegCreateKey(hCompanyKey, TEXT("DigitalJigsawMaker"), &hAppKey); + if (hAppKey == NULL) + { + RegCloseKey(hCompanyKey); + return FALSE; // 失敗。 + } + + // レジストリにデータを設定する。 +#define SET_REG_DATA(id) do { \ + auto& str = m_settings[TEXT(#id)]; \ + DWORD cbText = (str.size() + 1) * sizeof(WCHAR); \ + RegSetValueEx(hAppKey, TEXT(#id), 0, REG_SZ, (LPBYTE)str.c_str(), cbText); \ +} while(0) + SET_REG_DATA(IDC_PAGE_SIZE); + SET_REG_DATA(IDC_PAGE_DIRECTION); +#undef SET_REG_DATA + + // レジストリキーを閉じる。 + RegCloseKey(hAppKey); + RegCloseKey(hCompanyKey); + + return TRUE; // 成功。 +} + +// 文字列中に見つかった部分文字列をすべて置き換える。 +template +inline bool +str_replace(T_STR& str, const T_STR& from, const T_STR& to) +{ + bool ret = false; + size_t i = 0; + for (;;) { + i = str.find(from, i); + if (i == T_STR::npos) + break; + ret = true; + str.replace(i, from.size(), to); + i += to.size(); + } + return ret; +} +template +inline bool +str_replace(T_STR& str, + const typename T_STR::value_type *from, + const typename T_STR::value_type *to) +{ + return str_replace(str, T_STR(from), T_STR(to)); +} + +// ファイルサイズを取得する。 +DWORD get_file_size(const string_t& filename) +{ + WIN32_FIND_DATA find; + HANDLE hFind = FindFirstFile(filename.c_str(), &find); + if (hFind == INVALID_HANDLE_VALUE) + return 0; // エラー。 + FindClose(hFind); + if (find.nFileSizeHigh) + return 0; // 大きすぎるのでエラー。 + return find.nFileSizeLow; // ファイルサイズ。 +} + +// libHaruのエラーハンドラの実装。 +void hpdf_error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) { + char message[1024]; + StringCchPrintfA(message, _countof(message), "error: error_no = %04X, detail_no = %d", + UINT(error_no), INT(detail_no)); + throw std::runtime_error(message); +} + +// 長方形を描画する。 +void hpdf_draw_box(HPDF_Page page, double x, double y, double width, double height) +{ + HPDF_Page_MoveTo(page, x, y); + HPDF_Page_LineTo(page, x, y + height); + HPDF_Page_LineTo(page, x + width, y + height); + HPDF_Page_LineTo(page, x + width, y); + HPDF_Page_ClosePath(page); + HPDF_Page_Stroke(page); +} + +// 画像を描く。 +bool hpdf_draw_image(HPDF_Doc pdf, HPDF_Page page, double x, double y, double width, double height, const string_t& filename, + DWORD max_data_size, bool dont_resize_small) +{ + // ファイルサイズを取得する。 + DWORD file_size = get_file_size(filename); + if (file_size == 0) + return false; + + // 画像をHBITMAPとして読み込む。 + int image_width, image_height; + HBITMAP hbm = doLoadPic(filename.c_str(), &image_width, &image_height); + if (hbm == NULL) + return false; + + // JPEG/TIFFではないビットマップ画像については特別扱いで画像の品質を向上させることができる。 + bool png_is_better = false; + auto dotext = PathFindExtension(filename.c_str()); + if (lstrcmpi(dotext, TEXT(".png")) == 0 || + lstrcmpi(dotext, TEXT(".gif")) == 0 || + lstrcmpi(dotext, TEXT(".bmp")) == 0 || + lstrcmpi(dotext, TEXT(".dib")) == 0 || + lstrcmpi(dotext, TEXT(".ico")) == 0) + { + png_is_better = true; + } + + TempFile tempfile; + for (;;) + { + file_size = 0; + + // 一時ファイルを作成し、画像を保存する。 + if (png_is_better) + tempfile.init(TEXT("GN2"), TEXT(".png")); + else + tempfile.init(TEXT("GN2"), TEXT(".jpg")); + if (!gpimage_save(tempfile.make(), hbm)) + break; + + // ファイルサイズを取得する。 + file_size = get_file_size(tempfile.get()); + if (file_size == 0) + break; // 失敗。 + + // ファイルサイズは妥当か? + if (max_data_size == 0 || file_size <= max_data_size) + break; // 妥当。 + + // 画像サイズを縮小した画像を作成する。ゼロにならないように補正する。 + image_width /= 2; + image_height /= 2; + if (image_width <= 0) + image_width = 1; + if (image_height <= 0) + image_height = 1; + HBITMAP hbmNew = gpimage_resize(hbm, image_width, image_height); + if (hbmNew == NULL) + { + file_size = 0; + break; + } + + // hbmを縮小した画像に置き換える。 + ::DeleteObject(hbm); + hbm = hbmNew; + + // 一時ファイルを削除。 + tempfile.erase(); + } + + // ファイルサイズがゼロなら、失敗。 + if (file_size == 0) + { + ::DeleteObject(hbm); + return false; // 失敗。 + } + + // 画像を読み込む。 + HPDF_Image image; + if (png_is_better) + image = HPDF_LoadPngImageFromFile(pdf, ansi_from_wide(CP_ACP, tempfile.get())); + else + image = HPDF_LoadJpegImageFromFile(pdf, ansi_from_wide(CP_ACP, tempfile.get())); + if (image == NULL) + { + ::DeleteObject(hbm); + return false; // 失敗。 + } + + // 画像サイズとアスペクト比に従って処理を行う。 + double aspect1 = (double)image_width / (double)image_height; + double aspect2 = width / height; + double stretch_width, stretch_height; + if (image_width <= width && image_height <= height && dont_resize_small) + { + // 小さい画像を拡大しない。 + stretch_width = image_width; + stretch_height = image_height; + } + else + { + // 大きい画像はアスペクト比に従ってセルいっぱいに縮小する。 + if (aspect1 <= aspect2) + { + stretch_height = height; + stretch_width = height * aspect1; + } + else + { + stretch_width = width; + stretch_height = width / aspect1; + } + } + + // ゼロにならないように補正する。 + if (stretch_width <= 0) + stretch_width = 1; + if (stretch_height <= 0) + stretch_height = 1; + + // 画像を配置する。 + double dx = (width - stretch_width) / 2; + double dy = (height - stretch_height) / 2; + HPDF_Page_DrawImage(page, image, x + dx, y + dy, stretch_width, stretch_height); + + // HBITMAPを破棄する。 + ::DeleteObject(hbm); + + return true; +} + +// テキストを描画する。 +void hpdf_draw_text(HPDF_Page page, HPDF_Font font, double font_size, + const char *text, + double x, double y, double width, double height, + int draw_box = 0) +{ + // フォントサイズを制限。 + if (font_size > HPDF_MAX_FONTSIZE) + font_size = HPDF_MAX_FONTSIZE; + + // 長方形を描画する。 + if (draw_box == 1) + { + hpdf_draw_box(page, x, y, width, height); + } + + // 長方形に収まるフォントサイズを計算する。 + double text_width, text_height; + for (;;) + { + // フォントとフォントサイズを指定。 + HPDF_Page_SetFontAndSize(page, font, font_size); + + // テキストの幅と高さを取得する。 + text_width = HPDF_Page_TextWidth(page, text); + text_height = HPDF_Page_GetCurrentFontSize(page); + + // テキストが長方形に収まるか? + if (text_width <= width && text_height <= height) + { + // x,yを中央そろえ。 + x += (width - text_width) / 2; + y += (height - text_height) / 2; + break; + } + + // フォントサイズを少し小さくして再計算。 + font_size *= 0.8; + } + + // テキストを描画する。 + HPDF_Page_BeginText(page); + { + // ベースラインからdescentだけずらす。 + double descent = -HPDF_Font_GetDescent(font) * font_size / 1000.0; + HPDF_Page_TextOut(page, x, y + descent, text); + } + HPDF_Page_EndText(page); + + // 長方形を描画する。 + if (draw_box == 2) + { + hpdf_draw_box(page, x, y, text_width, text_height); + } +} + +// メインディッシュ処理。 +string_t JigsawMaker::JustDoIt(HWND hwnd) +{ + string_t ret; + // PDFオブジェクトを作成する。 + HPDF_Doc pdf = HPDF_New(hpdf_error_handler, NULL); + if (!pdf) + return L""; + + try + { + // エンコーディング 90ms-RKSJ-H, 90ms-RKSJ-V, 90msp-RKSJ-H, EUC-H, EUC-V が利用可能となる + HPDF_UseJPEncodings(pdf); + +#ifdef UTF8_SUPPORT + // エンコーディング "UTF-8" が利用可能に??? + HPDF_UseUTFEncodings(pdf); + HPDF_SetCurrentEncoder(pdf, "UTF-8"); +#endif + + // 日本語フォントの MS-(P)Mincyo, MS-(P)Gothic が利用可能となる + //HPDF_UseJPFonts(pdf); + + // 用紙の向き。 + HPDF_PageDirection direction; + if (SETTING(IDC_PAGE_DIRECTION) == doLoadString(IDS_PORTRAIT)) + direction = HPDF_PAGE_PORTRAIT; + else if (SETTING(IDC_PAGE_DIRECTION) == doLoadString(IDS_LANDSCAPE)) + direction = HPDF_PAGE_LANDSCAPE; + else + direction = HPDF_PAGE_LANDSCAPE; + + // ページサイズ。 + HPDF_PageSizes page_size; + if (SETTING(IDC_PAGE_SIZE) == doLoadString(IDS_A3)) + page_size = HPDF_PAGE_SIZE_A3; + else if (SETTING(IDC_PAGE_SIZE) == doLoadString(IDS_A4)) + page_size = HPDF_PAGE_SIZE_A4; + else if (SETTING(IDC_PAGE_SIZE) == doLoadString(IDS_A5)) + page_size = HPDF_PAGE_SIZE_A5; + else if (SETTING(IDC_PAGE_SIZE) == doLoadString(IDS_B4)) + page_size = HPDF_PAGE_SIZE_B4; + else if (SETTING(IDC_PAGE_SIZE) == doLoadString(IDS_B5)) + page_size = HPDF_PAGE_SIZE_B5; + else + page_size = HPDF_PAGE_SIZE_A4; + + // フォント名。 + string_t font_name; + if (m_font_map.size()) + { + for (auto& entry : m_font_map) + { + if (entry.m_font_name != SETTING(IDC_FONT_NAME)) + continue; + + auto ansi = ansi_from_wide(CP_ACP, entry.m_pathname.c_str()); + if (entry.m_index != -1) + { + std::string font_name_a = HPDF_LoadTTFontFromFile2(pdf, ansi, entry.m_index, HPDF_TRUE); + font_name = wide_from_ansi(CP_UTF8, font_name_a.c_str()); + } + else + { + std::string font_name_a = HPDF_LoadTTFontFromFile(pdf, ansi, HPDF_TRUE); + font_name = wide_from_ansi(CP_UTF8, font_name_a.c_str()); + } + } + } + else + { + if (SETTING(IDC_FONT_NAME) == doLoadString(IDS_FONT_01)) + font_name = TEXT("MS-PGothic"); + else if (SETTING(IDC_FONT_NAME) == doLoadString(IDS_FONT_02)) + font_name = TEXT("MS-PMincho"); + else if (SETTING(IDC_FONT_NAME) == doLoadString(IDS_FONT_03)) + font_name = TEXT("MS-Gothic"); + else if (SETTING(IDC_FONT_NAME) == doLoadString(IDS_FONT_04)) + font_name = TEXT("MS-Mincho"); + else + font_name = TEXT("MS-PGothic"); + } + + HPDF_Page page; // ページオブジェクト。 + HPDF_Font font; // フォントオブジェクト。 + INT iColumn = 0, iRow = 0, iPage = 0; // セル位置とページ番号。 + INT cItems = INT(m_list.size()); // 項目数。 + INT cPages = (cItems + (columns * rows) - 1) / (columns * rows); // ページ総数。 + double page_width, page_height; // ページサイズ。 + double content_x, content_y, content_width, content_height; // ページ内容の位置とサイズ。 + for (INT iItem = 0; iItem < cItems; ++iItem) + { + if (iColumn == 0 && iRow == 0) // 項目がページの最初ならば + { + // ページを追加する。 + page = HPDF_AddPage(pdf); + + // ページサイズと用紙の向きを指定。 + HPDF_Page_SetSize(page, page_size, direction); + + // ページサイズ(ピクセル単位)。 + page_width = HPDF_Page_GetWidth(page); + page_height = HPDF_Page_GetHeight(page); + + // ページ内容の位置とサイズ。 + content_x = margin; + content_y = margin; + content_width = page_width - margin * 2; + content_height = page_height - margin * 2; + + // 線の幅を指定。 + HPDF_Page_SetLineWidth(page, border_width); + + // 線の色を RGB で設定する。PDF では RGB 各値を [0,1] で指定することになっている。 + HPDF_Page_SetRGBStroke(page, 0, 0, 0); + + /* 塗りつぶしの色を RGB で設定する。PDF では RGB 各値を [0,1] で指定することになっている。*/ + HPDF_Page_SetRGBFill(page, 0, 0, 0); + + // フォントを指定する。 + auto font_name_a = ansi_from_wide(CP932, font_name.c_str()); +#ifdef UTF8_SUPPORT + font = HPDF_GetFont(pdf, font_name_a, "UTF-8"); +#else + font = HPDF_GetFont(pdf, font_name_a, "90ms-RKSJ-H"); +#endif + +#ifndef NO_SHAREWARE + // シェアウェア未登録ならば、ロゴ文字列を描画する。 + if (!g_shareware.IsRegistered()) + { +#ifdef UTF8_SUPPORT + auto logo_a = ansi_from_wide(CP_UTF8, doLoadString(IDS_LOGO)); +#else + auto logo_a = ansi_from_wide(CP932, doLoadString(IDS_LOGO)); +#endif + double logo_x = content_x, logo_y = content_y; + + // フォントとフォントサイズを指定。 + HPDF_Page_SetFontAndSize(page, font, footer_height); + + // テキストを描画する。 + HPDF_Page_BeginText(page); + { + HPDF_Page_TextOut(page, logo_x, logo_y, logo_a); + } + HPDF_Page_EndText(page); + } +#endif + // ヘッダー(ページ見出し)を描画する。 + if (header.size()) + { + string_t text = header; + substitute_tags(hwnd, text, m_list[iItem], iItem, cItems, iPage, cPages); +#ifdef UTF8_SUPPORT + auto header_text_a = ansi_from_wide(CP_UTF8, text.c_str()); +#else + auto header_text_a = ansi_from_wide(CP932, text.c_str()); +#endif + hpdf_draw_text(page, font, footer_height, header_text_a, + content_x, content_y + content_height - footer_height, + content_width, footer_height); + // ヘッダーの分だけページ内容のサイズを縮小する。 + content_height -= footer_height; + } + + // フッター(ページ番号)を描画する。 + if (page_numbers || footer.size()) + { + string_t text = footer; + substitute_tags(hwnd, text, m_list[iItem], iItem, cItems, iPage, cPages); +#ifdef UTF8_SUPPORT + auto footer_text_a = ansi_from_wide(CP_UTF8, text.c_str()); +#else + auto footer_text_a = ansi_from_wide(CP932, text.c_str()); +#endif + hpdf_draw_text(page, font, footer_height, footer_text_a, + content_x, content_y, + content_width, footer_height); + // フッターの分だけページ内容のサイズを縮小する。 + content_y += footer_height; + content_height -= footer_height; + } + + // 枠線を描く。 + if (draw_border) + { + hpdf_draw_box(page, content_x, content_y, content_width, content_height); + } + } + + // セルの位置とサイズ。 + double cell_width = content_width / columns; + double cell_height = content_height / rows; + double cell_x = content_x + cell_width * iColumn; + double cell_y = content_y + cell_height * (rows - iRow - 1); + + // セルの枠線を描く。 + if (draw_border) + { + hpdf_draw_box(page, cell_x, cell_y, cell_width, cell_height); + + // 枠線の分だけセルを縮小。 + cell_x += border_width; + cell_y += border_width; + cell_width -= border_width * 2; + cell_height -= border_width * 2; + } + + // テキストを描画する。 + if (image_title.size()) + { + // タグを規則に従って置き換える。 + string_t text = image_title; + substitute_tags(hwnd, text, m_list[iItem], iItem, cItems, iPage, cPages); + + // ANSI文字列に変換してテキストを描画する。 +#ifdef UTF8_SUPPORT + auto text_a = ansi_from_wide(CP_UTF8, text.c_str()); +#else + auto text_a = ansi_from_wide(CP932, text.c_str()); +#endif + hpdf_draw_text(page, font, font_size, text_a, cell_x, cell_y, cell_width, font_size); + + // セルのサイズを縮小する。 + cell_y += font_size; + cell_height -= font_size; + } + + // 画像を描く。 + hpdf_draw_image(pdf, page, cell_x, cell_y, cell_width, cell_height, m_list[iItem], + max_data_size, dont_resize_small); + + // 次のセルに進む。必要ならばページ番号を進める。 + ++iColumn; + if (iColumn == columns) + { + iColumn = 0; + ++iRow; + if (iRow == rows) + { + iRow = 0; + ++iPage; + } + } + } + + { + // 出力ファイル名のタグを置き換える。 + string_t text = output_name; + substitute_tags(hwnd, text, m_list[0], 0, cItems, 0, cPages, true); + + // ファイル名に使えない文字を置き換える。 + validate_filename(text); + + // PDFを一時ファイルに保存する。 + TempFile temp_file(TEXT("GN2"), TEXT(".pdf")); + std::string temp_file_a = ansi_from_wide(CP_ACP, temp_file.make()); + HPDF_SaveToFile(pdf, temp_file_a.c_str()); + + // デスクトップにファイルをコピー。 + TCHAR szPath[MAX_PATH]; + SHGetSpecialFolderPath(hwnd, szPath, CSIDL_DESKTOPDIRECTORY, FALSE); + PathAppend(szPath, text.c_str()); + StringCchCat(szPath, _countof(szPath), TEXT(".pdf")); + if (!CopyFile(temp_file.get(), szPath, FALSE)) + { + auto err_msg = ansi_from_wide(CP_ACP, doLoadString(IDS_COPYFILEFAILED)); + throw std::runtime_error(err_msg); + } + + // 成功メッセージを表示。 + StringCchCopy(szPath, _countof(szPath), text.c_str()); + StringCchCat(szPath, _countof(szPath), TEXT(".pdf")); + TCHAR szText[MAX_PATH]; + StringCchPrintf(szText, _countof(szText), doLoadString(IDS_SUCCEEDED), szPath); + ret = szText; + } + } + catch (std::runtime_error& err) + { + // 失敗。 + auto wide = wide_from_ansi(CP_ACP, err.what()); + MessageBoxW(hwnd, wide, NULL, MB_ICONERROR); + return TEXT(""); + } + + // PDFオブジェクトを解放する。 + HPDF_Free(pdf); + + return ret; +} + +// WM_INITDIALOG +// ダイアログの初期化。 +BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) +{ + // ユーザデータ。 + JigsawMaker* pJM = (JigsawMaker*)lParam; + + // ユーザーデータをウィンドウハンドルに関連付ける。 + SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); + + // gpimageライブラリを初期化する。 + gpimage_init(); + + // ファイルドロップを受け付ける。 + DragAcceptFiles(hwnd, TRUE); + + // アプリの名前。 + LoadString(NULL, IDS_APPNAME, g_szAppName, _countof(g_szAppName)); + + // アイコンの設定。 + g_hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(1)); + g_hIconSm = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(1), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + SendMessage(hwnd, WM_SETICON, ICON_BIG, (WPARAM)g_hIcon); + SendMessage(hwnd, WM_SETICON, ICON_SMALL, (WPARAM)g_hIconSm); + + // 初期化。 + pJM->InitDialog(hwnd); + + // レジストリからデータを読み込む。 + pJM->DataFromReg(hwnd); + + // ダイアログにデータを設定。 + pJM->DialogFromData(hwnd, FALSE); + + // Susie プラグインを読み込む。 + CHAR szPathA[MAX_PATH]; + GetModuleFileNameA(NULL, szPathA, _countof(szPathA)); + PathRemoveFileSpecA(szPathA); + if (!g_susie.load(szPathA)) + { + PathAppendA(szPathA, "plugins"); + g_susie.load(szPathA); + } + + return TRUE; +} + +// 「OK」ボタンが押された。 +BOOL OnOK(HWND hwnd) +{ + // ユーザデータ。 + JigsawMaker* pJM = (JigsawMaker*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + // 「処理中...」とボタンに表示する。 + HWND hButton = GetDlgItem(hwnd, IDC_GENERATE); + SetWindowText(hButton, doLoadString(IDS_PROCESSINGNOW)); + + // ダイアログからデータを取得。 + if (!pJM->DataFromDialog(hwnd, TRUE)) // 失敗。 + { + // ボタンテキストを元に戻す。 + SetWindowText(hButton, doLoadString(IDS_GENERATE)); + + return FALSE; // 失敗。 + } + + // 設定をレジストリに保存。 + pJM->RegFromData(hwnd); + + // メインディッシュ処理。 + string_t success = pJM->JustDoIt(hwnd); + + // ボタンテキストを元に戻す。 + SetWindowText(hButton, doLoadString(IDS_GENERATE)); + + // 必要なら結果をメッセージボックスとして表示する。 + if (success.size()) + { + MessageBox(hwnd, success.c_str(), g_szAppName, MB_ICONINFORMATION); + } + + return TRUE; // 成功。 +} + +// 「設定の初期化」ボタン。 +void OnEraseSettings(HWND hwnd) +{ + // ユーザーデータ。 + JigsawMaker* pJM = (JigsawMaker*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + // データをリセットする。 + pJM->Reset(); + + // データからダイアログへ。 + pJM->DialogFromData(hwnd, FALSE); + + // データからレジストリへ。 + pJM->RegFromData(hwnd); +} + +// WM_COMMAND +// コマンド。 +void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) +{ + switch (id) + { + case IDC_GENERATE: // 「PDF生成」ボタン。 + OnOK(hwnd); + break; + case IDC_EXIT: // 「終了」ボタン。 + EndDialog(hwnd, id); + break; + } +} + +// WM_DROPFILES +// ファイルがドロップされた。 +void OnDropFiles(HWND hwnd, HDROP hdrop) +{ + // ドロップ完了。 + DragFinish(hdrop); +} + +// WM_DESTROY +// ウィンドウが破棄された。 +void OnDestroy(HWND hwnd) +{ + // アイコンを破棄。 + DestroyIcon(g_hIcon); + DestroyIcon(g_hIconSm); + g_hIcon = g_hIconSm = NULL; + + // gpimageライブラリを終了する。 + gpimage_exit(); +} + +// ダイアログプロシージャ。 +INT_PTR CALLBACK +DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + HANDLE_MSG(hwnd, WM_INITDIALOG, OnInitDialog); + HANDLE_MSG(hwnd, WM_COMMAND, OnCommand); + HANDLE_MSG(hwnd, WM_DROPFILES, OnDropFiles); + HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy); + } + return 0; +} + +// ガゾーナラベのメイン関数。 +INT JigsawMaker_Main(HINSTANCE hInstance, INT argc, LPTSTR *argv) +{ + // アプリのインスタンスを保持する。 + g_hInstance = hInstance; + + // 共通コントロール群を初期化する。 + InitCommonControls(); + +#ifndef NO_SHAREWARE + // デバッガ―が有効、またはシェアウェアを開始できないときは + if (IsDebuggerPresent() || !g_shareware.Start(NULL)) + { + // 失敗。アプリケーションを終了する。 + return -1; + } +#endif + + // ユーザーデータを保持する。 + JigsawMaker gn(hInstance, argc, argv); + + // ユーザーデータをパラメータとしてダイアログを開く。 + DialogBoxParam(hInstance, MAKEINTRESOURCE(1), NULL, DialogProc, (LPARAM)&gn); + + // 正常終了。 + return 0; +} + +// Windowsアプリのメイン関数。 +INT WINAPI +WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + INT nCmdShow) +{ +#ifdef UNICODE + // wWinMainをサポートしていないコンパイラのために、コマンドラインの処理を行う。 + INT argc; + LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc); + INT ret = JigsawMaker_Main(hInstance, argc, argv); + LocalFree(argv); + return ret; +#else + return JigsawMaker_Main(hInstance, __argc, __argv); +#endif +} diff --git a/Jigsaw_res.rc b/Jigsaw_res.rc new file mode 100644 index 0000000..42934a7 --- /dev/null +++ b/Jigsaw_res.rc @@ -0,0 +1,110 @@ +// Jigsaw_res.rc +// This file is automatically generated by RisohEditor. +// † <-- This dagger helps UTF-8 detection. + +#include "resource.h" +#define APSTUDIO_HIDDEN_SYMBOLS +#include +#include +#undef APSTUDIO_HIDDEN_SYMBOLS +#pragma code_page(65001) // UTF-8 + +////////////////////////////////////////////////////////////////////////////// + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +////////////////////////////////////////////////////////////////////////////// +// RT_MANIFEST + +#ifndef MSVC +1 24 "res/Manifest_1.manifest" +#endif + +////////////////////////////////////////////////////////////////////////////// + +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT + +////////////////////////////////////////////////////////////////////////////// +// RT_DIALOG + +1 DIALOG 0, 0, 275, 135 +CAPTION "デジタルジグソーメーカー Ver.0.0.0 by 片山博文MZ" +STYLE DS_CENTER | DS_MODALFRAME | WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX +FONT 9, "メイリオ" +{ + LTEXT "用紙サイズ:", stc1, 5, 5, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + COMBOBOX cmb1, 70, 5, 95, 300, CBS_HASSTRINGS | CBS_AUTOHSCROLL | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "ページの向き:", stc2, 5, 20, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + COMBOBOX cmb2, 70, 20, 95, 300, CBS_HASSTRINGS | CBS_AUTOHSCROLL | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "外枠(mm):", stc3, 5, 35, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + COMBOBOX cmb3, 70, 35, 95, 300, CBS_HASSTRINGS | CBS_AUTOHSCROLL | CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "終了", IDCANCEL, 220, 110, 50, 20 + LTEXT "ピースの幅(mm):", stc4, 5, 50, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + COMBOBOX cmb4, 70, 50, 55, 300, CBS_HASSTRINGS | CBS_AUTOHSCROLL | CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "ピースの高さ(mm):", stc5, 135, 50, 70, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + COMBOBOX cmb5, 215, 50, 55, 300, CBS_HASSTRINGS | CBS_AUTOHSCROLL | CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "背景画像:", stc6, 5, 65, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + EDITTEXT edt1, 70, 65, 200, 12, ES_AUTOHSCROLL + PUSHBUTTON "参照...", psh1, 210, 80, 60, 12 + LTEXT "乱数の種:", stc7, 5, 95, 65, 10, SS_CENTERIMAGE | SS_NOTIFY | NOT WS_GROUP + EDITTEXT edt2, 70, 95, 41, 12, ES_AUTOHSCROLL + CONTROL "", scr1, "msctls_updown32", UDS_AUTOBUDDY | UDS_ALIGNRIGHT | UDS_SETBUDDYINT, 109, 99, 13, 13 + DEFPUSHBUTTON "生成", IDOK, 150, 110, 65, 20 + PUSHBUTTON "設定の初期化", psh2, 5, 115, 80, 15 +} + +////////////////////////////////////////////////////////////////////////////// +// RT_VERSION + +1 VERSIONINFO +FILEVERSION 0, 0, 0, 0 +PRODUCTVERSION 0, 0, 0, 0 +FILEOS 0x40004 +FILETYPE 0x1 +{ + BLOCK "StringFileInfo" + { + BLOCK "041103A4" + { + VALUE "CompanyName", "片山博文MZ\0" + VALUE "FileDescription", "デジタルジグソーメーカー\0" + VALUE "FileVersion", "0.0.0\0" + VALUE "LegalCopyright", "(c) 2023 katahiromz\0" + VALUE "ProductName", "デジタルジグソーメーカー\0" + VALUE "ProductVersion", "0.0.0\0" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0411, 0x03A4 + } +} + +////////////////////////////////////////////////////////////////////////////// +// TEXTINCLUDE + +#ifdef APSTUDIO_INVOKED + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include \r\n" + "#include \r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +////////////////////////////////////////////////////////////////////////////// diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5f5234f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +# デジタルジグソーメーカーの使用許諾契約書 + +Copyright (c) 2023 Katayama Hirofumi MZ . +All Rights Reserved. + +## 概要 + +本ソフトウェア「デジタルジグソーメーカー」は、試用期間のある1200円(手数料等別途)のシェアウェアです。 +継続して使用する場合はソフト配布サイトのVector(ベクター)で「ライセンスキー」(パスワード)を購入下さい。 + +「ライセンスキー」を入力すると、試用期間が終了し、制限が解除されます。 + +## 購入場所 + +- Vector https://www.vector.co.jp のガゾーナラベPDFのページ。 + +## 試用期間 + +10日間。 + +## 制限内容 + +試用期間中は、ページの左下に『「デジタルジグソーメーカーPDF」買ってね』と出力されます。 + +## 免責事項 + +このプログラムを使って生じた如何なる損害も当方は責任を負いません。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c2a019 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# デジタルジグソーメーカー by 片山博文MZ + +## これは何? + +「デジタルジグソーメーカー」は、画像を使って自動でジグソーパズルを作成するソフトです。 + +## 対応環境 + +- 日本語 Windows XP/Vista/7/10/11 + +## 対応画像形式 + +- JPEG/PNG/GIF/TIFF/BMP/WMF/EMF/ICO/CUR +- Susie プラグインで読み込める画像ファイル (起動前にプラグインを同じフォルダに置いて下さい) + +## 注意 + +- ユーザー名に機種依存文字が含まれている場合はサポート対象外です。 +- Web標準ではないファイル形式には脆弱性がある恐れがあります。 + +## 使用許諾 + +このプログラムは、試用期間のある1200円(手数料等別途)のシェアウェアです。 +継続して使用する場合はソフト配布サイトのVector(ベクター)でお支払い下さい。 +詳しくはファイルLICENSE.txtをご覧下さい。 + +このプログラムを使って生じた如何なる損害も当方は責任を負いません。 + +## 連絡先 + +電子メール katayama.hirofumi.mz@gmail.com でご連絡下さい。 + +ホームページ:https://katahiromz.web.fc2.com/ diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..5c2a019 --- /dev/null +++ b/README.txt @@ -0,0 +1,33 @@ +# デジタルジグソーメーカー by 片山博文MZ + +## これは何? + +「デジタルジグソーメーカー」は、画像を使って自動でジグソーパズルを作成するソフトです。 + +## 対応環境 + +- 日本語 Windows XP/Vista/7/10/11 + +## 対応画像形式 + +- JPEG/PNG/GIF/TIFF/BMP/WMF/EMF/ICO/CUR +- Susie プラグインで読み込める画像ファイル (起動前にプラグインを同じフォルダに置いて下さい) + +## 注意 + +- ユーザー名に機種依存文字が含まれている場合はサポート対象外です。 +- Web標準ではないファイル形式には脆弱性がある恐れがあります。 + +## 使用許諾 + +このプログラムは、試用期間のある1200円(手数料等別途)のシェアウェアです。 +継続して使用する場合はソフト配布サイトのVector(ベクター)でお支払い下さい。 +詳しくはファイルLICENSE.txtをご覧下さい。 + +このプログラムを使って生じた如何なる損害も当方は責任を負いません。 + +## 連絡先 + +電子メール katayama.hirofumi.mz@gmail.com でご連絡下さい。 + +ホームページ:https://katahiromz.web.fc2.com/ diff --git a/SHA-256.cpp b/SHA-256.cpp new file mode 100644 index 0000000..276d4b4 --- /dev/null +++ b/SHA-256.cpp @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////// +// SHA-256.cpp -- SHA-256 hash generator +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// + +#include "SHA-256.hpp" + +//////////////////////////////////////////////////////////////////////////// + +static const SHA256_DWORD s_sha256_h[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, + 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +static const SHA256_DWORD s_sha256_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, + 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, + 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, + 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, + 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, + 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, + 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +//////////////////////////////////////////////////////////////////////////// + +inline SHA256_DWORD SHA256_HiLong(SHA256_QWORD q) { + return static_cast(q >> 32); +} + +inline SHA256_DWORD SHA256_LoLong(SHA256_QWORD q) { + return static_cast(q); +} + +inline SHA256_DWORD mzc_shr(SHA256_DWORD x, std::size_t n) { + assert(n < 32); + return (x >> n); +} + +inline SHA256_DWORD mzc_rotr(SHA256_DWORD x, std::size_t n) { + assert(n < 32); + return static_cast((x >> n) | (x << (32 - n))); +} + +inline SHA256_DWORD mzc_ch(SHA256_DWORD x, SHA256_DWORD y, SHA256_DWORD z) { + return (x & y) ^ (~x & z); +} + +inline SHA256_DWORD mzc_maj(SHA256_DWORD x, SHA256_DWORD y, SHA256_DWORD z) { + return (x & y)^(x & z)^(y & z); +} + +inline SHA256_DWORD mzc_bsig0(SHA256_DWORD x) { + return mzc_rotr(x, 2) ^ mzc_rotr(x, 13) ^ mzc_rotr(x, 22); +} + +inline SHA256_DWORD mzc_bsig1(SHA256_DWORD x) { + return mzc_rotr(x, 6) ^ mzc_rotr(x, 11) ^ mzc_rotr(x, 25); +} + +inline SHA256_DWORD mzc_ssig0(SHA256_DWORD x) { + return mzc_rotr(x, 7) ^ mzc_rotr(x, 18) ^ mzc_shr(x, 3); +} + +inline SHA256_DWORD mzc_ssig1(SHA256_DWORD x) { + return mzc_rotr(x, 17) ^ mzc_rotr(x, 19) ^ mzc_shr(x, 10); +} + +//////////////////////////////////////////////////////////////////////////// + +void MSha256::Init() { + using namespace std; + assert(sizeof(m_h) == sizeof(s_sha256_h)); + memcpy(m_h, s_sha256_h, sizeof(s_sha256_h)); + + m_iw = 0; + m_len = 0; + memset(m_w, 0, 256); +} + +void MSha256::UpdateTable() { + using namespace std; + SHA256_DWORD x[8]; + + memcpy(x, m_h, 32); + SHA256_DWORD t1, t2; + for (int t = 0; t < 64; t += 8) { +#define MZC_ROUND(n, a, b, c, d, e, f, g, h) \ + t1 = x[h] + mzc_bsig1(x[e]) + mzc_ch(x[e], x[f], x[g]) + \ + s_sha256_k[t + n] + m_w[t + n]; \ + t2 = mzc_bsig0(x[a]) + mzc_maj(x[a], x[b], x[c]); \ + x[d] += t1; \ + x[h] = t1 + t2 + MZC_ROUND(0, 0, 1, 2, 3, 4, 5, 6, 7); + MZC_ROUND(1, 7, 0, 1, 2, 3, 4, 5, 6); + MZC_ROUND(2, 6, 7, 0, 1, 2, 3, 4, 5); + MZC_ROUND(3, 5, 6, 7, 0, 1, 2, 3, 4); + MZC_ROUND(4, 4, 5, 6, 7, 0, 1, 2, 3); + MZC_ROUND(5, 3, 4, 5, 6, 7, 0, 1, 2); + MZC_ROUND(6, 2, 3, 4, 5, 6, 7, 0, 1); + MZC_ROUND(7, 1, 2, 3, 4, 5, 6, 7, 0); +#undef MZC_ROUND + } + + for (int i = 0; i < 8; ++i) { + m_h[i] += x[i]; + } +} + +void MSha256::AddData(const void* ptr, size_t len) { + using namespace std; + assert(ptr || len == 0); + SHA256_DWORD dwLen = static_cast(len); + + const SHA256_BYTE *pb = reinterpret_cast(ptr); + +push_top: + SHA256_DWORD index = 0; + SHA256_DWORD t_4; + + for (t_4 = m_iw; (m_iw % 4) != 0 && index < dwLen; ++t_4) { + m_w[t_4 / 4] |= pb[index] << (24 - (t_4 % 4) * 8); + ++index; + } + m_iw = t_4; + + SHA256_DWORD t; + for (t = m_iw / 4; t < 16 && index + 3 < dwLen; ++t) { + m_w[t] |= (pb[index] << 24) | + (pb[index + 1] << 16) | + (pb[index + 2] << 8) | + (pb[index + 3]); + index += 4; + } + + for (t_4 = t * 4; t_4 < 64 && index < dwLen; ++t_4) { + m_w[t_4 / 4] |= pb[index++] << (24 - (t_4 % 4) * 8); + } + m_iw = t_4; + + if (t_4 == 64) { + for (int t = 16; t < 64; ++t) { + m_w[t] = mzc_ssig1(m_w[t - 2]) + m_w[t - 7] + + mzc_ssig0(m_w[t - 15]) + m_w[t - 16]; + } + UpdateTable(); + + m_iw = 0; + memset(m_w, 0, 256); + m_len += index * 8; + if (index < dwLen) { + pb += index; + dwLen -= index; + goto push_top; + } + } + else { + m_len += dwLen * 8; + } +} + +void MSha256::GetHashBinary(void *p32bytes) { + assert(p32bytes); + SHA256_BYTE *pb = reinterpret_cast(p32bytes); + + m_w[m_iw / 4] |= 0x80000000 >> ((m_iw % 4) * 8); + m_iw++; + while (m_iw % 4) { + ++m_iw; + } + + if (m_iw > 56) { + for (int t = 16; t < 64; ++t) + { + m_w[t] = mzc_ssig1(m_w[t - 2]) + m_w[t - 7] + + mzc_ssig0(m_w[t - 15]) + m_w[t - 16]; + } + UpdateTable(); + m_iw = 0; + } + + for (int t = m_iw / 4; t < 14; ++t) { + m_w[t] = 0; + } + + m_w[14] = SHA256_HiLong(m_len); + m_w[15] = SHA256_LoLong(m_len); + + for (int t = 16; t < 64; ++t) { + m_w[t] = mzc_ssig1(m_w[t - 2]) + m_w[t - 7] + + mzc_ssig0(m_w[t - 15]) + m_w[t - 16]; + } + UpdateTable(); + + for (int t = 0; t < 32; t++) { + pb[t] = static_cast(m_h[t / 4] >> ((3 - t % 4) * 8)); + } +} + +//////////////////////////////////////////////////////////////////////////// +// test and sample + +#ifdef SHA256_UNITTEST + #include + #include + #include + + void print_binary(const void *p, size_t size) { + static const char *s_hex = "0123456789abcdef"; + const BYTE *pb = reinterpret_cast(p); + std::cout << "{"; + while (size--) { + BYTE b = *pb; + std::cout << "0x" << s_hex[b >> 4] << s_hex[b & 0xF] << ", "; + ++pb; + } + std::cout << "}" << std::endl; + } + + void print_binary(const char *psz) { + print_binary(psz, ::lstrlenA(psz)); + } + + int main(void) { + // interactive mode + std::string salt, line; + std::cout << "Enter 'exit' to exit." << std::endl; + + std::cout << "Enter salt string: "; + std::getline(std::cin, salt); + if (salt == "exit" || salt == "quit") + return 0; + + std::string result; + for (;;) { + std::cout << "Enter password: "; + std::getline(std::cin, line); + if (line == "exit" || line == "quit") + break; + + MzcGetSha256HexString(result, line.data(), salt.data()); + std::cout << result << std::endl; + + char binary[32]; + MzcGetSha256Binary(binary, line.data(), salt.data()); + print_binary(binary, 32); + } + + return 0; + } +#endif // def SHA256_UNITTEST + +//////////////////////////////////////////////////////////////////////////// diff --git a/SHA-256.hpp b/SHA-256.hpp new file mode 100644 index 0000000..2da547c --- /dev/null +++ b/SHA-256.hpp @@ -0,0 +1,175 @@ +//////////////////////////////////////////////////////////////////////////// +// SHA-256.hpp -- SHA-256 hash generator +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// +// See FIP180-2 at: +// http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +//////////////////////////////////////////////////////////////////////////// +// Thanks, Naoki_I. +//////////////////////////////////////////////////////////////////////////// + +#ifndef __MZC3_SHA_256_HPP__ +#define __MZC3_SHA_256_HPP__ + +#include // for strlen +#include // for assert +#include // for std::distance + +//////////////////////////////////////////////////////////////////////////// +// little endian or big endian? + +#if !defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN) + #if defined(_WIN32) || defined(MSDOS) || defined(__i386__) + #define LITTLE_ENDIAN + #else + #define BIG_ENDIAN + #endif +#endif + +#if defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN) + #error You lose! +#endif + +#ifdef BIG_ENDIAN + #pragma message("MZC3_SHA-256: Please tell the author whether it works well or not.") +#endif + +//////////////////////////////////////////////////////////////////////////// +// SHA256_BYTE, SHA256_DWORD, SHA256_QWORD + +typedef unsigned char SHA256_BYTE; +typedef unsigned long SHA256_DWORD; + +#if __cplusplus >= 201103L + typedef unsigned long long SHA256_QWORD; +#else + typedef unsigned __int64 SHA256_QWORD; +#endif + +//////////////////////////////////////////////////////////////////////////// +// MzcHexStringFromBytes + +#ifndef MzcHexStringFromBytes + template + void MzcHexStringFromBytes(T_STRING& str, T_ITER begin_, T_ITER end_) { + typedef typename T_STRING::value_type T_CHAR; + static const char s_hex[] = "0123456789abcdef"; + for (str.clear(); begin_ != end_; ++begin_) + { + const unsigned char by = *begin_; + str += static_cast(s_hex[by >> 4]); + str += static_cast(s_hex[by & 0x0F]); + } + } + + template + inline void MzcHexStringFromBytes(T_STRING& str, const T_CONTAINER& bytes) { + MzcHexStringFromBytes(str, bytes.begin(), bytes.end()); + } + + #define MzcHexStringFromBytes MzcHexStringFromBytes +#endif // ndef MzcHexStringFromBytes + +//////////////////////////////////////////////////////////////////////////// +// MSha256 + +class MSha256 +{ +public: + MSha256() { Init(); } + MSha256(const char *salt) { + using namespace std; + assert(salt); + Init(); + AddData(salt, strlen(salt)); + } + + // The control order: + // Init() --> AddData() --> GetHashBinary() or GetHashHexString() + + void Init(); + + void AddData(const void* ptr, size_t len); + + template + void AddData(T_ITER begin_, T_ITER end_) { + std::size_t len = std::distance(begin_, end_) * sizeof(*begin_); + AddData(&*begin_, len); + } + + void GetHashBinary(void* p32bytes); + + template + void GetHashHexString(T_STRING& str) { + char hash[32]; + GetHashBinary(hash); + MzcHexStringFromBytes(str, hash, hash + 32); + } + +protected: + SHA256_DWORD m_h[8]; + SHA256_DWORD m_w[64]; + SHA256_DWORD m_iw; + SHA256_QWORD m_len; + + void UpdateTable(); +}; + +//////////////////////////////////////////////////////////////////////////// +// MzcGetSha256Binary and MzcGetSha256HexString + +inline void MzcGetSha256Binary( + void *p32bytes, const void *ptr, size_t len, + const char *salt = "") +{ + assert(ptr || len == 0); + MSha256 sha256(salt); + sha256.AddData(ptr, len); + sha256.GetHashBinary(p32bytes); +} + +inline void MzcGetSha256Binary( + void *p32bytes, const char *psz, const char *salt = "") +{ + using namespace std; + MzcGetSha256Binary(p32bytes, psz, strlen(psz), salt); +} + +template +inline void MzcGetSha256HexString( + T_STRING& str, const void *ptr, size_t len, + const char *salt = "") +{ + assert(ptr || len == 0); + MSha256 sha256(salt); + sha256.AddData(ptr, len); + sha256.GetHashHexString(str); +} + +template +inline void MzcGetSha256HexString( + T_STRING& str, T_ITER begin_, T_ITER end_, const char *salt) +{ + assert(salt); + MSha256 sha256(salt); + sha256.AddData(begin_, end_); + sha256.GetHashHexString(str); +} + +template +inline void +MzcGetSha256HexString( + T_STRING& str, const char *psz, const char *salt = "") +{ + using namespace std; + assert(psz); + size_t len = strlen(psz); + MSha256 sha256(salt); + sha256.AddData(salt, strlen(salt)); + sha256.AddData(psz, len); + sha256.GetHashHexString(str); +} + +//////////////////////////////////////////////////////////////////////////// + +#endif // ndef __MZC3_SHA_256_HPP__ diff --git a/Shareware.cpp b/Shareware.cpp new file mode 100644 index 0000000..786248b --- /dev/null +++ b/Shareware.cpp @@ -0,0 +1,1027 @@ +//////////////////////////////////////////////////////////////////////////// +// Shareware.cpp -- MZC3 shareware maker for Win32 +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// + +#ifndef MZC_NO_SHAREWARE + +#include +#include +#include +#include +#include + +#include "SHA-256.hpp" +#include "Shareware.hpp" + +using namespace std; + +#ifdef MZC_NO_INLINING + #undef MZC_INLINE + #define MZC_INLINE /*empty*/ + #include "Shareware_inl.hpp" +#endif + +static LPCTSTR s_pszSoftware = TEXT("Software"); +static LPCTSTR s_pszStartUse = TEXT("SW_StartUse"); +static LPCTSTR s_pszCheckSum = TEXT("SW_CheckSum"); +static const char *s_pszOldVersion = "SW_OldVersion"; +static const char *s_pszEncodedPassword = "SW_EncodedPassword"; + +//////////////////////////////////////////////////////////////////////////// + +LPTSTR SwLoadStringDx1(HINSTANCE hInstance, UINT uID) +{ + static TCHAR s_sz[2048]; + s_sz[0] = 0; + ::LoadString(hInstance, uID, s_sz, 2048); + assert(::lstrlen(s_sz) < 2048); + return s_sz; +} + +LPTSTR SwLoadStringDx2(HINSTANCE hInstance, UINT uID) +{ + static TCHAR s_sz[2048]; + s_sz[0] = 0; + ::LoadString(hInstance, uID, s_sz, 2048); + assert(::lstrlen(s_sz) < 2048); + return s_sz; +} + +//////////////////////////////////////////////////////////////////////////// +// SwCenterDialog + +void SwCenterDialog(HWND hwnd) +{ + assert(hwnd); + assert(::IsWindow(hwnd)); + BOOL bChild = !!(::GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD); + + HWND hwndOwner; + if (bChild) + hwndOwner = ::GetParent(hwnd); + else + hwndOwner = ::GetWindow(hwnd, GW_OWNER); + + RECT rc, rcOwner; + if (hwndOwner) + ::GetWindowRect(hwndOwner, &rcOwner); + else + ::SystemParametersInfo(SPI_GETWORKAREA, 0, &rcOwner, 0); + + ::GetWindowRect(hwnd, &rc); + + POINT pt; + pt.x = rcOwner.left + + ((rcOwner.right - rcOwner.left) - (rc.right - rc.left)) / 2; + pt.y = rcOwner.top + + ((rcOwner.bottom - rcOwner.top) - (rc.bottom - rc.top)) / 2; + + if (bChild && hwndOwner != NULL) + ::ScreenToClient(hwndOwner, &pt); + + ::SetWindowPos(hwnd, NULL, pt.x, pt.y, 0, 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + ::SendMessage(hwnd, DM_REPOSITION, 0, 0); +} + +//////////////////////////////////////////////////////////////////////////// +// SwCenterMessageBox + +static HHOOK s_hMsgBoxHook = NULL; + +static LRESULT CALLBACK +SwMsgBoxCbtProc_(int nCode, WPARAM wParam, LPARAM /*lParam*/) +{ + if (nCode == HCBT_ACTIVATE) + { + HWND hwnd = (HWND)wParam; + TCHAR szClassName[MAX_PATH]; + ::GetClassName(hwnd, szClassName, MAX_PATH); + if (lstrcmpi(szClassName, TEXT("#32770")) == 0) + { + SwCenterDialog(hwnd); + + if (s_hMsgBoxHook != NULL && ::UnhookWindowsHookEx(s_hMsgBoxHook)) + s_hMsgBoxHook = NULL; + } + } + return 0; +} + +int SwCenterMessageBox( + HWND hwndParent, LPCTSTR pszText, LPCTSTR pszCaption, UINT uMB_) +{ + if (s_hMsgBoxHook != NULL && ::UnhookWindowsHookEx(s_hMsgBoxHook)) + s_hMsgBoxHook = NULL; + + HINSTANCE hInst = reinterpret_cast( + ::GetWindowLongPtr(hwndParent, GWLP_HINSTANCE)); + + DWORD dwThreadID = ::GetCurrentThreadId(); + s_hMsgBoxHook = ::SetWindowsHookEx(WH_CBT, SwMsgBoxCbtProc_, hInst, dwThreadID); + + int nID = ::MessageBox(hwndParent, pszText, pszCaption, uMB_); + + if (s_hMsgBoxHook != NULL && ::UnhookWindowsHookEx(s_hMsgBoxHook)) + s_hMsgBoxHook = NULL; + + return nID; +} + +//////////////////////////////////////////////////////////////////////////// +// SwMakeStaticHyperlink + +struct SW_HyperlinkStatic +{ + WNDPROC fnWndProc; + #ifdef UNICODE + std::wstring strURL; + #else + std::string strURL; + #endif + HCURSOR hHandCursor; +}; + +static LRESULT CALLBACK +HyperlinkStatic_OnPaint(HWND hwnd, HDC hdc) +{ + // fill background + HWND hwndParent = ::GetParent(hwnd); + LRESULT result = ::SendMessage( + hwndParent, WM_CTLCOLORSTATIC, + reinterpret_cast(hdc), + reinterpret_cast(hwnd)); + HBRUSH hbrBackground = reinterpret_cast(result); + RECT rc; + ::GetClientRect(hwnd, &rc); + ::FillRect(hdc, &rc, hbrBackground); + + // create underlined font + result = ::SendMessage(hwnd, WM_GETFONT, 0, 0); + HFONT hFont = reinterpret_cast(result); + LOGFONT lf; + ::GetObject(hFont, sizeof(lf), &lf); + lf.lfUnderline = TRUE; + hFont = ::CreateFontIndirect(&lf); + + // set text color and background mix mode + ::SetTextColor(hdc, RGB(0, 0, 192)); + ::SetBkMode(hdc, TRANSPARENT); + + // draw text + HGDIOBJ hFontOld = ::SelectObject(hdc, hFont); + { + TCHAR szText[256]; + ::GetWindowText(hwnd, szText, 256); + UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_END_ELLIPSIS; + DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); + if (style & SS_CENTER) + uFormat |= DT_CENTER; + else if (style & SS_RIGHT) + uFormat |= DT_RIGHT; + else + uFormat |= DT_LEFT; + + RECT rcText = rc; + ::DrawText(hdc, szText, -1, &rcText, uFormat | DT_CALCRECT); + + INT nClientWidth = rc.right - rc.left; + INT nTextWidth = rcText.right - rcText.left; + if (style & SS_CENTER) + { + rcText.left += (nClientWidth - nTextWidth) / 2; + rcText.right += (nClientWidth - nTextWidth) / 2; + ::OffsetRect(&rcText, 0, 1); + } + else if (style & SS_RIGHT) + { + rcText.left += nClientWidth - nTextWidth; + rcText.right += nClientWidth - nTextWidth; + ::OffsetRect(&rcText, -1, 1); + } + else + { + ::OffsetRect(&rcText, 1, 1); + } + + // draw focus + ::InflateRect(&rcText, 1, 1); + if (hwnd == ::GetFocus()) + ::DrawFocusRect(hdc, &rcText); + ::InflateRect(&rcText, -1, -1); + + ::DrawText(hdc, szText, -1, &rc, uFormat); + } + ::DeleteObject(::SelectObject(hdc, hFontOld)); + return 0; +} + +static void +HyperlinkStatic_OnTab(HWND hwnd) +{ + if (::GetAsyncKeyState(VK_SHIFT) < 0) + { + HWND hCtrl = hwnd; + for (;;) + { + hCtrl = ::GetNextWindow(hCtrl, GW_HWNDPREV); + if (hCtrl == NULL) + hCtrl = ::GetWindow(hwnd, GW_HWNDLAST); + if (hCtrl == hwnd) + break; + if ((::GetWindowLong(hCtrl, GWL_STYLE) & WS_TABSTOP) == 0) + continue; + ::SetFocus(hCtrl); + break; + } + } + else + { + HWND hCtrl = hwnd; + for (;;) + { + hCtrl = ::GetNextWindow(hCtrl, GW_HWNDNEXT); + if (hCtrl == NULL) + hCtrl = ::GetWindow(hwnd, GW_HWNDFIRST); + if (hCtrl == hwnd) + break; + if ((::GetWindowLong(hCtrl, GWL_STYLE) & WS_TABSTOP) == 0) + continue; + ::SetFocus(hCtrl); + break; + } + } +} + +static LRESULT CALLBACK +HyperlinkStatic_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + SW_HyperlinkStatic *pHS; + LRESULT result; + + pHS = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + switch (uMsg) + { + case WM_ERASEBKGND: + return 0; + + case WM_LBUTTONDOWN: + ::SetFocus(hwnd); + ::ShellExecute(hwnd, NULL, pHS->strURL.data(), NULL, NULL, SW_SHOWNORMAL); + break; + + case WM_MOUSEMOVE: + if (pHS->hHandCursor) + ::SetCursor(pHS->hHandCursor); + break; + + case WM_KEYDOWN: + if (wParam == VK_SPACE || wParam == VK_RETURN) + { + ::ShellExecute(hwnd, NULL, pHS->strURL.data(), NULL, NULL, SW_SHOWNORMAL); + return 0; + } + if (wParam == VK_TAB) + { + HyperlinkStatic_OnTab(hwnd); + return 0; + } + if (wParam == VK_ESCAPE) + { + ::PostMessage(::GetParent(hwnd), WM_COMMAND, IDCANCEL, + reinterpret_cast(hwnd)); + return 0; + } + return ::CallWindowProc(pHS->fnWndProc, hwnd, uMsg, wParam, lParam); + + case WM_GETDLGCODE: + result = ::CallWindowProc(pHS->fnWndProc, hwnd, uMsg, wParam, lParam); + return result | DLGC_WANTALLKEYS | DLGC_WANTTAB; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = ::BeginPaint(hwnd, &ps); + if (hdc) + { + HyperlinkStatic_OnPaint(hwnd, hdc); + ::EndPaint(hwnd, &ps); + } + } + return 0; + + case WM_NCDESTROY: + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + ::CallWindowProc(pHS->fnWndProc, hwnd, uMsg, wParam, lParam); + delete pHS; + return 0; + + case WM_SETFOCUS: + case WM_KILLFOCUS: + ::InvalidateRect(hwnd, NULL, TRUE); + break; + } + return ::CallWindowProc(pHS->fnWndProc, hwnd, uMsg, wParam, lParam); +} + +void SwMakeStaticHyperlink(HWND hwndCtrl, LPCTSTR pszURL/* = NULL*/) +{ + DWORD style = ::GetWindowLong(hwndCtrl, GWL_STYLE); + style |= SS_NOPREFIX | SS_NOTIFY | WS_TABSTOP; + ::SetWindowLong(hwndCtrl, GWL_STYLE, style); + + SW_HyperlinkStatic *pHS = new SW_HyperlinkStatic; + if (pszURL) + { + pHS->strURL = pszURL; + } + else + { + TCHAR sz[256]; + ::GetWindowText(hwndCtrl, sz, 256); + pHS->strURL = sz; + } + pHS->hHandCursor = + ::LoadCursor(::GetModuleHandle(NULL), MAKEINTRESOURCE(32731)); + + ::SetWindowLongPtr(hwndCtrl, GWLP_USERDATA, + reinterpret_cast(pHS)); + pHS->fnWndProc = + reinterpret_cast( + ::SetWindowLongPtr(hwndCtrl, GWLP_WNDPROC, + reinterpret_cast(HyperlinkStatic_WndProc))); +} + +//////////////////////////////////////////////////////////////////////////// + +SW_Shareware::SW_Shareware( + LPCTSTR pszCompanyKey, + LPCTSTR pszAppKey, + const char *pszSha256HashHexString, + DWORD dwTrialDays/* = 15*/, + const char *salt/* = ""*/, + const char *new_version/* = ""*/) + : m_hInstance(::GetModuleHandle(NULL)), + m_dwTrialDays(dwTrialDays), + m_status(SW_Shareware::IN_TRIAL), + m_strCompanyKey(pszCompanyKey), + m_strAppKey(pszAppKey), + m_strSha256HashHexString(pszSha256HashHexString), + m_strSalt(salt), + m_strNewVersion(new_version), + m_strOldVersion() +{ +} + +SW_Shareware::SW_Shareware( + LPCTSTR pszCompanyKey, + LPCTSTR pszAppKey, + const BYTE *pbHash32Bytes, + DWORD dwTrialDays/* = 15*/, + const char *salt/* = ""*/, + const char *new_version/* = ""*/) + : m_hInstance(::GetModuleHandle(NULL)), + m_dwTrialDays(dwTrialDays), + m_status(SW_Shareware::IN_TRIAL), + m_strCompanyKey(pszCompanyKey), + m_strAppKey(pszAppKey), + m_strSha256HashHexString(), + m_strSalt(salt), + m_strNewVersion(new_version), + m_strOldVersion() +{ + MzcHexStringFromBytes( + m_strSha256HashHexString, pbHash32Bytes, pbHash32Bytes + 32); +} + +/*virtual*/ SW_Shareware::~SW_Shareware() +{ +} + +bool SW_Shareware::Start(HWND hwndParent/* = NULL*/) +{ + CheckRegistry(hwndParent); + + switch (m_status) + { + case SW_Shareware::IN_TRIAL_FIRST_TIME: + OnTrialFirstTime(hwndParent); + return true; + + case SW_Shareware::IN_TRIAL: + OnTrial(hwndParent); + return true; + + case SW_Shareware::REGD: + return true; + + case SW_Shareware::OUT_OF_TRIAL: + return OnOutOfTrial(hwndParent); + + default: + return false; + } +} + +void SwUrgeRegisterDlg_OnInit(HWND hwnd, SW_Shareware *pShareware) +{ + LPTSTR pszMsg; + TCHAR szMsg[MAX_PATH * 3]; + pShareware->CheckDate(); + + switch (pShareware->m_status) + { + case SW_Shareware::IN_TRIAL_FIRST_TIME: + pszMsg = SwLoadStringDx1(pShareware->m_hInstance, 32732); + ::wsprintf(szMsg, pszMsg, pShareware->m_dwTrialDays); + ::SetDlgItemText(hwnd, edt1, szMsg); + break; + + case SW_Shareware::IN_TRIAL: + if (pShareware->m_dwlTotalMinutesRemains >= 60 * 24) + { + pszMsg = SwLoadStringDx1(pShareware->m_hInstance, 32732); + ::wsprintf(szMsg, pszMsg, + static_cast( + pShareware->m_dwlTotalMinutesRemains / (60 * 24))); + ::SetDlgItemText(hwnd, edt1, szMsg); + } + else + { + pszMsg = SwLoadStringDx1(pShareware->m_hInstance, 32733); + ::wsprintf(szMsg, pszMsg, + static_cast( + pShareware->m_dwlTotalMinutesRemains / 60), + static_cast( + pShareware->m_dwlTotalMinutesRemains % 60)); + ::SetDlgItemText(hwnd, edt1, szMsg); + } + break; + + case SW_Shareware::OUT_OF_TRIAL: + pszMsg = SwLoadStringDx1(pShareware->m_hInstance, 32734); + ::SetDlgItemText(hwnd, edt1, pszMsg); + break; + + case SW_Shareware::REGD: + ::EnableWindow(::GetDlgItem(hwnd, edt2), FALSE); + ::EnableWindow(::GetDlgItem(hwnd, IDOK), FALSE); + pszMsg = SwLoadStringDx1(pShareware->m_hInstance, 32735); + ::SetDlgItemText(hwnd, edt1, pszMsg); + ::SetFocus(::GetDlgItem(hwnd, IDCANCEL)); + break; + } +} + +static INT_PTR CALLBACK +SwUrgeRegisterDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static SW_Shareware *s_pShareware = NULL; + char szPassword[sw_shareware_max_password]; + + switch (uMsg) + { + case WM_INITDIALOG: + SwCenterDialog(hwnd); + s_pShareware = reinterpret_cast(lParam); + SwMakeStaticHyperlink(hwnd, stc1, NULL); + SwUrgeRegisterDlg_OnInit(hwnd, s_pShareware); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + szPassword[0] = 0; + ::GetDlgItemTextA(hwnd, edt2, szPassword, sw_shareware_max_password); + if (s_pShareware->RegisterPassword(hwnd, szPassword)) + { + SwUrgeRegisterDlg_OnInit(hwnd, s_pShareware); + TCHAR szTitle[256]; + ::GetWindowText(hwnd, szTitle, 256); + SwCenterMessageBox(hwnd, + SwLoadStringDx1(s_pShareware->m_hInstance, 32735), + szTitle, MB_ICONINFORMATION); + } + else + { + s_pShareware->ShowErrorMessage(hwnd, 32731); + } + break; + + case IDCANCEL: + ::EndDialog(hwnd, IDCANCEL); + break; + } + } + return 0; +} + +/*virtual*/ bool SW_Shareware::UrgeRegister(HWND hwndParent/* = NULL*/) +{ + ::DialogBoxParam(m_hInstance, MAKEINTRESOURCE(32731), + hwndParent, SwUrgeRegisterDlgProc, + reinterpret_cast(this)); + return IsRegistered(); +} + +bool SW_Shareware::CheckRegistry(HWND hwndParent) +{ + LONG result; + HKEY hkeySoftware, hkeyCompany, hkeyApp; + DWORD dwDisp; + bool bHasAppKey = false; + bool bCanUse = false; + + result = ::RegCreateKeyEx(HKEY_CURRENT_USER, s_pszSoftware, 0, + NULL, 0, KEY_READ | KEY_WRITE, NULL, + &hkeySoftware, &dwDisp); + if (result == ERROR_SUCCESS) + { + result = ::RegOpenKeyEx(hkeySoftware, + m_strCompanyKey.data(), 0, + KEY_READ | KEY_WRITE, &hkeyCompany); + if (result == ERROR_SUCCESS) + { + result = ::RegOpenKeyEx(hkeyCompany, m_strAppKey.data(), 0, + KEY_READ | KEY_WRITE, &hkeyApp); + if (result == ERROR_SUCCESS) + { + bHasAppKey = true; + + bCanUse = CheckAppKey(hwndParent, hkeyApp); + ::RegCloseKey(hkeyApp); + } + ::RegCloseKey(hkeyCompany); + } + ::RegCloseKey(hkeySoftware); + } + + if (!bHasAppKey) + { + SetRegistryFirstTime(hwndParent); + m_status = SW_Shareware::IN_TRIAL_FIRST_TIME; + bCanUse = true; + } + + return bCanUse; +} + +bool SW_Shareware::SetRegistryFirstTime(HWND hwndParent) +{ + LONG result; + HKEY hkeySoftware, hkeyCompany, hkeyApp; + DWORD dwDisp; + bool bSuccess = false; + + result = ::RegCreateKeyEx(HKEY_CURRENT_USER, s_pszSoftware, 0, + NULL, 0, KEY_READ | KEY_WRITE, NULL, + &hkeySoftware, &dwDisp); + if (result == ERROR_SUCCESS) + { + result = ::RegCreateKeyEx(hkeySoftware, m_strCompanyKey.data(), + 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hkeyCompany, &dwDisp); + if (result == ERROR_SUCCESS) + { + result = ::RegCreateKeyEx(hkeyCompany, m_strAppKey.data(), + 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hkeyApp, &dwDisp); + if (result == ERROR_SUCCESS) + { + FILETIME ft; + ::GetSystemTimeAsFileTime(&ft); + + DWORD cb = static_cast(sizeof(ft)); + result = ::RegSetValueEx(hkeyApp, s_pszStartUse, + 0, REG_BINARY, reinterpret_cast(&ft), cb); + if (result == ERROR_SUCCESS) + { + result = ::RegQueryValueEx( + hkeyApp, s_pszStartUse, NULL, + NULL, NULL, NULL); + if (result == ERROR_SUCCESS) + { + bSuccess = true; + } + else + { + ShowErrorMessage(hwndParent, 32736); + } + } + + ::RegDeleteValueA(hkeyApp, s_pszEncodedPassword); + + ::RegCloseKey(hkeyApp); + } + ::RegCloseKey(hkeyCompany); + } + ::RegCloseKey(hkeySoftware); + } + + return bSuccess; +} + +bool SW_Shareware::RegisterPassword(HWND hwndParent, const char *pszPassword) +{ + LONG result; + HKEY hkeySoftware, hkeyCompany, hkeyApp; + DWORD dwDisp; + bool bSuccess = false; + + // check password + if (!IsPasswordValid(pszPassword)) + { + ::Sleep(750); + return false; + } + + // duplicate and encode the password +#ifdef _MSC_VER + char *pszEncodedPassword = _strdup(pszPassword); +#else + char *pszEncodedPassword = strdup(pszPassword); +#endif + DWORD size = static_cast(::lstrlenA(pszEncodedPassword)); + EncodePassword(pszEncodedPassword, size); + +#ifndef NDEBUG + DecodePassword(pszEncodedPassword, size); + assert(memcmp(pszEncodedPassword, pszPassword, size) == 0); + EncodePassword(pszEncodedPassword, size); +#endif + + result = ::RegCreateKeyEx(HKEY_CURRENT_USER, s_pszSoftware, 0, + NULL, 0, KEY_READ | KEY_WRITE, NULL, + &hkeySoftware, &dwDisp); + if (result == ERROR_SUCCESS) + { + result = ::RegCreateKeyEx(hkeySoftware, m_strCompanyKey.data(), + 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hkeyCompany, &dwDisp); + if (result == ERROR_SUCCESS) + { + result = ::RegCreateKeyEx(hkeyCompany, m_strAppKey.data(), + 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hkeyApp, &dwDisp); + if (result == ERROR_SUCCESS) + { + bSuccess = true; + + // set password + DWORD cb = size; + result = ::RegSetValueExA(hkeyApp, s_pszEncodedPassword, 0, REG_BINARY, + reinterpret_cast(pszEncodedPassword), cb); + if (result != ERROR_SUCCESS) + { + bSuccess = false; + } + else + { + result = ::RegQueryValueExA(hkeyApp, s_pszEncodedPassword, NULL, + NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + { + ShowErrorMessage(hwndParent, 32736); + bSuccess = false; + } + } + + if (bSuccess) + { + // set check sum + DWORD dwCheckSum = GetUserCheckSum(); + cb = static_cast(sizeof(DWORD)); + result = ::RegSetValueEx(hkeyApp, s_pszCheckSum, 0, REG_DWORD, + reinterpret_cast(&dwCheckSum), cb); + if (result != ERROR_SUCCESS) + { + bSuccess = false; + } + else + { + result = ::RegQueryValueEx(hkeyApp, s_pszCheckSum, NULL, + NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + { + ShowErrorMessage(hwndParent, 32736); + bSuccess = false; + } + } + } + + ::RegCloseKey(hkeyApp); + } + ::RegCloseKey(hkeyCompany); + } + ::RegCloseKey(hkeySoftware); + } + + if (bSuccess) + { + m_status = SW_Shareware::REGD; + } + + free(pszEncodedPassword); + + return bSuccess; +} + +bool SW_Shareware::CheckAppKey(HWND hwndParent, HKEY hkeyApp) +{ + LONG result; + DWORD cb; + FILETIME ft; + + m_dwlTotalMinutesRemains = 0; + + // check password + char szPassword[sw_shareware_max_password]; + cb = sw_shareware_max_password - 1; + result = ::RegQueryValueExA(hkeyApp, s_pszEncodedPassword, NULL, + NULL, reinterpret_cast(&szPassword), &cb); + if (result == ERROR_SUCCESS) + { + szPassword[cb] = 0; + DecodePassword(szPassword, cb); + if (IsPasswordValid(szPassword)) + { + // check check sum + DWORD dwValue; + cb = static_cast(sizeof(DWORD)); + result = ::RegQueryValueEx(hkeyApp, s_pszCheckSum, NULL, + NULL, reinterpret_cast(&dwValue), &cb); + if (result == ERROR_SUCCESS && dwValue == GetUserCheckSum()) + { + m_status = SW_Shareware::REGD; + return true; + } + } + } + + // check version + char szOldVersion[64]; + cb = 64 - 1; + result = ::RegQueryValueExA(hkeyApp, s_pszOldVersion, NULL, + NULL, reinterpret_cast(&szOldVersion), &cb); + if (result == ERROR_SUCCESS) + { + m_strOldVersion = szOldVersion; + + // compare version + if (CompareVersion(m_strOldVersion.data(), + m_strNewVersion.data()) < 0) + { + m_status = SW_Shareware::IN_TRIAL; + + // update version + cb = static_cast(m_strNewVersion.size() + 1); + result = ::RegSetValueExA(hkeyApp, s_pszOldVersion, 0, REG_SZ, + reinterpret_cast(m_strNewVersion.data()), cb); + assert(result == ERROR_SUCCESS); + + // update date + ::GetSystemTimeAsFileTime(&ft); + m_ftStart = ft; + cb = static_cast(sizeof(ft)); + result = ::RegSetValueEx(hkeyApp, s_pszStartUse, 0, REG_BINARY, + reinterpret_cast(&ft), cb); + assert(result == ERROR_SUCCESS); + + return true; + } + } + else + { + // set version + cb = static_cast(m_strNewVersion.size() + 1); + result = ::RegSetValueExA(hkeyApp, s_pszOldVersion, 0, REG_SZ, + reinterpret_cast(m_strNewVersion.data()), cb); + assert(result == ERROR_SUCCESS); + } + + // check date + cb = static_cast(sizeof(ft)); + result = ::RegQueryValueEx(hkeyApp, s_pszStartUse, NULL, + NULL, reinterpret_cast(&ft), &cb); + if (result == ERROR_SUCCESS) + { + m_ftStart = ft; + if (CheckDate()) + { + m_status = SW_Shareware::IN_TRIAL; + return true; + } + else + { + m_status = SW_Shareware::OUT_OF_TRIAL; + return false; + } + } + else + { + // set date + ::GetSystemTimeAsFileTime(&ft); + m_ftStart = ft; + cb = static_cast(sizeof(ft)); + ::RegSetValueEx(hkeyApp, s_pszStartUse, 0, REG_BINARY, + reinterpret_cast(&ft), cb); + + // query date for registry check + LONG result; + result = ::RegQueryValueEx(hkeyApp, s_pszStartUse, NULL, + NULL, NULL, NULL); + if (result == ERROR_SUCCESS) + { + m_status = SW_Shareware::IN_TRIAL; + return true; + } + else + { + ShowErrorMessage(hwndParent, 32736); + return false; + } + } +} + +/*virtual*/ bool +SW_Shareware::IsPasswordValid(const char *pszPassword) const +{ + std::string str; + MzcGetSha256HexString(str, pszPassword, m_strSalt.data()); + return (str == m_strSha256HashHexString); +} + +bool SW_Shareware::CheckDate() +{ + ULARGE_INTEGER uli; + + uli.LowPart = m_ftStart.dwLowDateTime; + uli.HighPart = m_ftStart.dwHighDateTime; + DWORDLONG dwlStart = uli.QuadPart; + + FILETIME ftNow; + ::GetSystemTimeAsFileTime(&ftNow); + uli.LowPart = ftNow.dwLowDateTime; + uli.HighPart = ftNow.dwHighDateTime; + DWORDLONG dwlNow = uli.QuadPart; + + LONGLONG delta = GetTrialDays(); + delta *= 24 * 60 * 60; + delta *= 10000000; + + if (dwlStart + delta > dwlNow) + { + m_dwlTotalMinutesRemains = (dwlStart + delta) - dwlNow; + m_dwlTotalMinutesRemains /= 10000000; + m_dwlTotalMinutesRemains /= 60; + return true; + } + + m_dwlTotalMinutesRemains = 0; + return false; +} + +DWORD SW_Shareware::GetUserCheckSum() const +{ + DWORD dwCheckSum = 0xDeadFace; + TCHAR szUser[64]; + DWORD dwSize = 64; + if (::GetUserName(szUser, &dwSize)) + { + LPTSTR pch = szUser; + while (*pch) + { + dwCheckSum += *pch; + ++pch; + } + } + return dwCheckSum; +} + +/*virtual*/ void +SW_Shareware::EncodePassword(void *pass, DWORD size) const +{ + // TODO: + BYTE *pb = reinterpret_cast(pass); + while (size--) + { + *pb ^= 0xFF; + pb++; + } +} + +/*virtual*/ void +SW_Shareware::DecodePassword(void *pass, DWORD size) const +{ + // TODO: + BYTE *pb = reinterpret_cast(pass); + while (size--) + { + *pb ^= 0xFF; + pb++; + } +} + +static void sw_explode( + std::vector& v, + const std::string& separators, const std::string& s) +{ + size_t i = s.find_first_not_of(separators); + size_t n = s.size(); + + v.clear(); + while (i < n) + { + size_t stop = s.find_first_of(separators, i); + if (stop > n) stop = n; + v.push_back(s.substr(i, stop - i)); + i = s.find_first_not_of(separators, stop + 1); + } +} + +/*virtual*/ int +SW_Shareware::CompareVersion(const char *old_ver, const char *new_ver) +{ + using namespace std; + if (old_ver[0] == 0 && new_ver[0] == 0) + return 0; + + std::vector o, n; + sw_explode(o, " .", old_ver); + sw_explode(n, " .", new_ver); + + size_t osiz = o.size(), nsiz = n.size(); + size_t m = (osiz > nsiz ? nsiz : osiz); + for (size_t i = 0; i < m; ++i) + { + if (o[i].size() == 0 && n[i].size() == 0) continue; + if (o[i].size() == 0) return -1; + if (n[i].size() == 0) return -1; + + if (isdigit(o[i][0]) && isdigit(n[i][0])) + { + char *op, *np; + int on = strtol(o[i].data(), &op, 10); + int nn = strtol(n[i].data(), &np, 10); + if (on < nn) return -1; + if (on > nn) return 1; + + int cmp = strcmp(op, np); + if (cmp < 0) return -1; + if (cmp > 0) return 1; + } + else + { + int cmp = strcmp(o[i].data(), n[i].data()); + if (cmp < 0) return -1; + if (cmp > 0) return 1; + } + } + if (osiz < nsiz) return -1; + if (osiz > nsiz) return 1; + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +#ifdef SHAREWARE_UNITTEST + extern "C" + int main(void) + { + SW_Shareware s( + TEXT("Katayama Hirofumi MZ"), + TEXT("SharewareTest"), + "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); + assert(s.IsPasswordValid("test")); + + assert(s.CompareVersion("", "") == 0); + assert(s.CompareVersion("1", "1") == 0); + assert(s.CompareVersion("2.1", "2.1") == 0); + + assert(s.CompareVersion("1", "2") < 0); + assert(s.CompareVersion("1.1", "2.1") < 0); + assert(s.CompareVersion("1.1", "1.2") < 0); + assert(s.CompareVersion("1.1", "1.1.1") < 0); + assert(s.CompareVersion("1.1", "1.2.1") < 0); + assert(s.CompareVersion("1.1", "1.1a") < 0); + assert(s.CompareVersion("1.1a", "1.1b") < 0); + assert(s.CompareVersion("1.1", "1.1 b") < 0); + assert(s.CompareVersion("1.1 a", "1.1 b") < 0); + + assert(s.CompareVersion("2", "1") > 0); + assert(s.CompareVersion("2.1", "1.1") > 0); + assert(s.CompareVersion("1.2", "1.1") > 0); + assert(s.CompareVersion("1.1.1", "1.1") > 0); + assert(s.CompareVersion("1.2.1", "1.1") > 0); + assert(s.CompareVersion("1.1a", "1.1") > 0); + assert(s.CompareVersion("1.1b", "1.1a") > 0); + assert(s.CompareVersion("1.1 b", "1.1") > 0); + assert(s.CompareVersion("1.1 b", "1.1 a") > 0); + return 0; + } +#endif // def SHAREWARE_UNITTEST + +//////////////////////////////////////////////////////////////////////////// + +#endif // ndef MZC_NO_SHAREWARE diff --git a/Shareware.hpp b/Shareware.hpp new file mode 100644 index 0000000..692fde9 --- /dev/null +++ b/Shareware.hpp @@ -0,0 +1,131 @@ +//////////////////////////////////////////////////////////////////////////// +// Shareware.hpp -- MZC3 shareware maker for Win32 +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// + +#ifndef __MZC3_SHAREWARE_HPP__ +#define __MZC3_SHAREWARE_HPP__ +#ifndef MZC_NO_SHAREWARE + +#include + +//////////////////////////////////////////////////////////////////////////// + +#define sw_shareware_max_password 256 + +//////////////////////////////////////////////////////////////////////////// +// useful functions + +LPTSTR SwLoadStringDx1(HINSTANCE hInstance, UINT uID); +LPTSTR SwLoadStringDx2(HINSTANCE hInstance, UINT uID); +void SwCenterDialog(HWND hwnd); +int SwCenterMessageBox( + HWND hwndParent, LPCTSTR pszText, LPCTSTR pszCaption, UINT uMB_); +void SwMakeStaticHyperlink(HWND hwndCtrl, LPCTSTR pszURL = NULL); +void SwMakeStaticHyperlink( + HWND hwndParent, UINT idCtrl, LPCTSTR pszURL = NULL); + +//////////////////////////////////////////////////////////////////////////// + +#ifndef EXTENDS_MOBJECT + #define EXTENDS_MOBJECT +#endif + +class SW_Shareware EXTENDS_MOBJECT +{ +public: + // NOTE: pszCompanyKey is the name of the registry key of the company. + // NOTE: pszAppKey is the name of the registry key of the application. + // NOTE: dwTrialDays is the trial interval in days. + // NOTE: parameter salt is the salt string. + // NOTE: parameter new_version is the current version of this software. + SW_Shareware(LPCTSTR pszCompanyKey, + LPCTSTR pszAppKey, + const char *pszSha256HashHexString, + DWORD dwTrialDays = 15, + const char *salt = "", + const char *new_version = ""); + SW_Shareware(LPCTSTR pszCompanyKey, + LPCTSTR pszAppKey, + const BYTE *pbHash32Bytes, + DWORD dwTrialDays = 15, + const char *salt = "", + const char *new_version = ""); + virtual ~SW_Shareware(); + + // NOTE: SW_Shareware::Start must be called on start-up of the MZC3 shareware. + // NOTE: SW_Shareware::Start returns false if the application cannot be used. + virtual bool Start(HWND hwndParent = NULL); + + bool IsRegistered() const; + bool IsInTrial() const; + bool IsOutOfTrial() const; + + DWORD GetTrialDays() const; + bool IsPasswordValid(const char *pszPassword) const; + + // NOTE: UrgeRegister show a dialog and returns true if registered. + virtual bool UrgeRegister(HWND hwndParent = NULL); + bool RegisterPassword(HWND hwndParent, const char *pszPassword); + virtual void ShowErrorMessage(HWND hwndParent, UINT uStringID); + virtual void ThisCommandRequiresRegistering(HWND hwndParent); + bool CheckDate(); + + virtual int CompareVersion(const char *old_ver, const char *new_ver); + +public: + HINSTANCE m_hInstance; + DWORDLONG m_dwlTotalMinutesRemains; + DWORD m_dwTrialDays; + FILETIME m_ftStart; + + enum SHAREWARE_STATUS + { + IN_TRIAL_FIRST_TIME, IN_TRIAL, OUT_OF_TRIAL, REGD + }; + SHAREWARE_STATUS m_status; + +public: + #ifdef UNICODE + typedef std::wstring tstring; + #else + typedef std::string tstring; + #endif + +protected: + tstring m_strCompanyKey; + tstring m_strAppKey; + std::string m_strSha256HashHexString; + std::string m_strSalt; + std::string m_strNewVersion; + std::string m_strOldVersion; + + bool CheckRegistry(HWND hwndParent); + bool CheckAppKey(HWND hwndParent, HKEY hkeyApp); + + bool SetRegistryFirstTime(HWND hwndParent); + + DWORD GetUserCheckSum() const; + virtual void OnTrialFirstTime(HWND hwndParent); + virtual void OnTrial(HWND hwndParent); + virtual bool OnOutOfTrial(HWND hwndParent); + virtual void EncodePassword(void *pass, DWORD size) const; + virtual void DecodePassword(void *pass, DWORD size) const; + +private: + SW_Shareware(); + // NOTE: SW_Shareware is not copyable. + SW_Shareware(const SW_Shareware&); + SW_Shareware& operator=(const SW_Shareware&); +}; + +//////////////////////////////////////////////////////////////////////////// + +#ifndef MZC_NO_INLINING + #undef MZC_INLINE + #define MZC_INLINE inline + #include "Shareware_inl.hpp" +#endif + +#endif // ndef MZC_NO_SHAREWARE +#endif // ndef __MZC3_SHAREWARE_HPP__ diff --git a/Shareware.rc b/Shareware.rc new file mode 100644 index 0000000..19b9c25 --- /dev/null +++ b/Shareware.rc @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////// +// Shareware.rc -- MZC3 shareware maker for Win32 +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// + +#ifndef MZC_NO_SHAREWARE +#include +#include + +//////////////////////////////////////////////////////////////////////////// + +// UTF-8 +#pragma code_page(65001) + +//////////////////////////////////////////////////////////////////////////// + +32731 CURSOR "hand.cur" + +//////////////////////////////////////////////////////////////////////////// +// Japanese + +// Japanese +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT + +STRINGTABLE +{ + 32731, "ライセンス キーが間違っています。" + 32732, "本ソフトウェア「デジタルジグソーメーカー」は、試用期間のある1200円(手数料等別途)のシェアウェアです。残り%u日で試用期限が切れます。継続して利用したい場合は、ライセンス キーを登録する必要があります。\r\n\r\n「ライセンスキー」を入力すると、試用期間が終了し、制限が解除されます。試用期間中は、各ページの左下に『「ジグソーメーカー」買ってね』と出力されます。" + 32733, "本ソフトウェア「デジタルジグソーメーカー」は、試用期間のある1200円(手数料等別途)のシェアウェアです。残り%u時間%02u分で試用期限が切れます。継続して利用したい場合は、ライセンス キーを登録する必要があります。\r\n\r\n「ライセンスキー」を入力すると、試用期間が終了し、制限が解除されます。試用期間中は、各ページの左下に『「ジグソーメーカー」買ってね』と出力されます。" + 32734, "試用期限が切れました。継続して利用したい場合は、ライセンス キーを登録する必要があります。" + 32735, "ライセンス キーが登録されました。ありがとうございます。" + 32736, "レジストリが壊れているか、アクセスできません。" + 32737, "申し訳ありませんが、このコマンドを実行するには、ライセンス キーを登録する必要があります。" +} + +32731 DIALOG 0, 0, 240, 185 +STYLE DS_MODALFRAME | DS_CENTER | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +CAPTION "【ガゾーナラベPDF】 ライセンス キーの登録" +FONT 9, "MS Pゴシック" +{ + CONTROL "(ここに現在の状態が表示されます)", edt1, "EDIT", ES_LEFT | ES_READONLY | ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL, 5, 5, 230, 120 + // TODO: シェアウェア用のURLを指定して下さい。 + CONTROL "", stc1, "STATIC", SS_LEFT | SS_NOPREFIX | SS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_GROUP, 20, 130, 200, 12 + CONTROL "ライセンス キー(&K):", -1, "STATIC", SS_RIGHT | WS_CHILD | WS_VISIBLE | WS_GROUP, 5, 147, 65, 12 + // TODO: 必要ならば、ES_PASSWORD, ES_UPPERCASE, ES_LOWERCASEスタイルを追加して下さい。 + CONTROL "", edt2, "EDIT", ES_LEFT | ES_AUTOHSCROLL | ES_UPPERCASE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 75, 145, 100, 14 + CONTROL "登録(&R)", IDOK, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 145, 55, 14 + CONTROL "閉じる(&C)", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 90, 165, 60, 14 +} + +//////////////////////////////////////////////////////////////////////////// + +#endif // ndef MZC_NO_SHAREWARE diff --git a/Shareware_inl.hpp b/Shareware_inl.hpp new file mode 100644 index 0000000..3b3a1bc --- /dev/null +++ b/Shareware_inl.hpp @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////// +// Shareware_inl.hpp -- MZC3 shareware maker for Win32 +// This file is part of MZC3. See file "ReadMe.txt" and "License.txt". +//////////////////////////////////////////////////////////////////////////// + +MZC_INLINE void SwMakeStaticHyperlink( + HWND hwndParent, UINT idCtrl, LPCTSTR pszURL/* = NULL*/) +{ + SwMakeStaticHyperlink(::GetDlgItem(hwndParent, idCtrl), pszURL); +} + +MZC_INLINE DWORD SW_Shareware::GetTrialDays() const +{ + return m_dwTrialDays; +} + +MZC_INLINE bool SW_Shareware::IsRegistered() const +{ + return m_status == SW_Shareware::REGD; +} + +MZC_INLINE bool SW_Shareware::IsInTrial() const +{ + return m_status == SW_Shareware::IN_TRIAL || + m_status == SW_Shareware::IN_TRIAL_FIRST_TIME; +} + +MZC_INLINE bool SW_Shareware::IsOutOfTrial() const +{ + return m_status == SW_Shareware::OUT_OF_TRIAL; +} + +MZC_INLINE /*virtual*/ void SW_Shareware::OnTrialFirstTime(HWND hwndParent) +{ +} + +MZC_INLINE /*virtual*/ void SW_Shareware::OnTrial(HWND hwndParent) +{ + UrgeRegister(hwndParent); +} + +MZC_INLINE /*virtual*/ bool SW_Shareware::OnOutOfTrial(HWND hwndParent) +{ + return UrgeRegister(hwndParent); +} + +MZC_INLINE /*virtual*/ void +SW_Shareware::ThisCommandRequiresRegistering(HWND hwndParent) +{ + ShowErrorMessage(hwndParent, 32737); +} + +MZC_INLINE /*virtual*/ void SW_Shareware::ShowErrorMessage( + HWND hwndParent, UINT uStringID) +{ + SwCenterMessageBox(hwndParent, + SwLoadStringDx2(m_hInstance, uStringID), + NULL, MB_ICONERROR); +} + +//////////////////////////////////////////////////////////////////////////// diff --git a/Susie.hpp b/Susie.hpp new file mode 100644 index 0000000..fb8a823 --- /dev/null +++ b/Susie.hpp @@ -0,0 +1,329 @@ +// Susie.hpp --- Susieプラグイン管理 by katahiromz +#pragma once + +#ifndef _INC_WINDOWS + #include // Windowsの標準ヘッダ。 +#endif + +#include // std::string, std::wstring +#include // std::vector + +class SusiePlugin; +class SusiePluginManager; + +// Susieプラグインのクラス。 +class SusiePlugin +{ +public: + // API関数型の定義。 + typedef int (PASCAL *FN_GetPluginInfo)(int infono, LPSTR buf, int buflen); + typedef int (PASCAL *FN_IsSupported)(LPSTR filename, DWORD dw); + typedef int (PASCAL *FN_GetPicture)(LPSTR buf, long len, unsigned int flag, + HANDLE *pHBInfo, HANDLE *pHBm, + FARPROC lpPrgressCallback, long lData); + HINSTANCE m_hInst = NULL; // プラグインのインスタンス。 + + // API関数のポインタ。 + FN_GetPluginInfo GetPluginInfo = NULL; + FN_IsSupported IsSupported = NULL; + FN_GetPicture GetPicture = NULL; + + std::string m_filename; // ファイル名。 + std::string m_filter; // フィルター文字列。 + + // コンストラクタ。 + SusiePlugin() + { + } + + // SusiePluginクラスはコピー禁止。 + SusiePlugin(const SusiePlugin&) = delete; + SusiePlugin& operator=(const SusiePlugin&) = delete; + + // デストラクタ。 + ~SusiePlugin() + { + // 自動的に破棄する。 + unload(); + } + + // 破棄または初期化する。 + void unload() + { + m_filename.clear(); + m_filter.clear(); + if (m_hInst) + { + FreeLibrary(m_hInst); + m_hInst = NULL; + } + GetPluginInfo = NULL; + IsSupported = NULL; + GetPicture = NULL; + } + + // 拡張子がサポートされているか? + bool is_dotext_supported(LPCSTR dotext) const + { + std::vector extensions; + str_split(extensions, m_filter, std::string(";")); + for (size_t i = 0; i < extensions.size(); ++i) + { + const char *extension = extensions[i].c_str(); + if (*extension == '*') + ++extension; + if (lstrcmpiA(extension, dotext) == 0) + return true; + } + + return false; + } + + // プラグインを読み込む。 + bool load(const char *filename) + { + // 最初に初期化する。 + unload(); + + // プラグインをDLLファイルとして読み込む。 + m_hInst = LoadLibraryA(filename); + if (m_hInst == NULL) + return false; + + // DLLからAPI関数を取得する。 + GetPluginInfo = (FN_GetPluginInfo)GetProcAddress(m_hInst, "GetPluginInfo"); + IsSupported = (FN_IsSupported)GetProcAddress(m_hInst, "IsSupported"); + GetPicture = (FN_GetPicture)GetProcAddress(m_hInst, "GetPicture"); + if (!GetPluginInfo || !IsSupported || !GetPicture) + { + unload(); + return false; + } + + // APIバージョン情報を取得する。 + CHAR buf[512]; + GetPluginInfo(0, buf, 512); + std::string api_ver = buf; + + // 画像読み込み用のプラグインか? + if (api_ver.size() == 4 && api_ver[2] == 'I') + { + // フィルターを取得する。 + for (int i = 2; i < 64; i += 2) + { + if (!GetPluginInfo(i + 0, buf, 512)) + break; + if (m_filter.size()) + m_filter += ";"; + m_filter += buf; + } + } + + // フィルターがなければ破棄する。 + if (m_filter.empty()) + { + unload(); + return false; + } + + // ファイル名を格納する。 + m_filename = PathFindFileNameA(filename); + return true; + } + + // プラグインを使用して画像を読み込む。 + HBITMAP load_image(LPCSTR filename) + { + // API関数GetPictureを使って画像データを取得する。 + HLOCAL hBitmapInfo = NULL, hBits = NULL; + if (GetPicture((LPSTR)filename, 0, 0, (HANDLE*)&hBitmapInfo, (HANDLE*)&hBits, NULL, 0) != 0) + return NULL; + + // ハンドルをロックすることで、実際のポインタを取得できる。 + LPBITMAPINFO pbmi = (LPBITMAPINFO)LocalLock(hBitmapInfo); + LPBYTE pbBits = (LPBYTE)LocalLock(hBits); + + // DIBのHBITMAPを作成。 + LPVOID pBits; + HBITMAP hbm = CreateDIBSection(NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0); + + // ビット群をコピーする。 + BITMAP bm; + if (hbm && GetObject(hbm, sizeof(bm), &bm)) + CopyMemory(pBits, pbBits, bm.bmWidthBytes * bm.bmHeight); + + // アンロック・破棄する。 + LocalUnlock(hBitmapInfo); + LocalUnlock(hBits); + LocalFree(hBitmapInfo); + LocalFree(hBits); + + // ビットマップハンドルHBITMAPを返す。 + return hbm; + } + +protected: + // 文字列を区切りで分割する関数。 + template + static void + str_split(T_STR_CONTAINER& container, + const typename T_STR_CONTAINER::value_type& str, + const typename T_STR_CONTAINER::value_type& chars) + { + container.clear(); + size_t i = 0, k = str.find_first_of(chars); + while (k != T_STR_CONTAINER::value_type::npos) + { + container.push_back(str.substr(i, k - i)); + i = k + 1; + k = str.find_first_of(chars, i); + } + container.push_back(str.substr(i)); + } +}; + +// Susieプラグインの管理クラス。 +class SusiePluginManager +{ +public: + // Susieプラグインのファイル名群。 + std::vector m_plugin_filenames; + // Susieプラグインのパスファイル名群。 + std::vector m_plugin_pathnames; + // Susieプラグイン群を保持する変数。 + std::vector m_plugins; + + // コンストラクタ。 + SusiePluginManager() + { + } + + // デストラクタ。 + ~SusiePluginManager() + { + // 自動的に破棄する。 + unload(); + } + + // プラグインが読み込まれたか? + bool is_loaded() const + { + return m_plugins.size() > 0; + } + + // プラグイン群を破棄して、初期化する。 + void unload() + { + for (size_t i = 0; i < m_plugins.size(); ++i) + { + delete m_plugins[i]; + m_plugins[i] = NULL; + } + m_plugins.clear(); + + m_plugin_filenames.clear(); + m_plugin_pathnames.clear(); + } + + // ディレクトリ(フォルダ)からプラグイン群を読み込む。 + bool load(LPCSTR dir = ".") + { + // 最初に破棄・初期化する。 + unload(); + + // ワイルドカードを含む文字列を構築する。 + CHAR path[MAX_PATH]; + GetFullPathNameA(dir, MAX_PATH, path, NULL); + PathAppendA(path, "*.spi"); + + // パターンマッチにマッチした文字列を列挙して、プラグインの一覧を取得する。 + WIN32_FIND_DATAA find; + HANDLE hFind = FindFirstFileA(path, &find); + PathRemoveFileSpecA(path); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + m_plugin_filenames.push_back(find.cFileName); + PathAppendA(path, find.cFileName); + m_plugin_pathnames.push_back(path); + PathRemoveFileSpecA(path); + } while (FindNextFileA(hFind, &find)); + FindClose(hFind); + } + + // 実際にプラグインを読み込む。 + for (size_t i = 0; i < m_plugin_pathnames.size(); ++i) + { + SusiePlugin *plugin = new SusiePlugin(); + std::string& pathname = m_plugin_pathnames[i]; + if (!plugin->load(pathname.c_str())) + { + delete plugin; + continue; + } + m_plugins.push_back(plugin); + } + + // 1つでもプラグインを読み込めたら成功。 + return m_plugins.size() > 0; + } + + // 指定された拡張子がサポートされているか? + bool is_dotext_supported(LPCSTR dotext) const + { + for (size_t i = 0; i < m_plugins.size(); ++i) + { + SusiePlugin *plugin = m_plugins[i]; + if (plugin) + { + if (plugin->is_dotext_supported(dotext)) + { + return true; + } + } + } + return false; + } + + // プラグイン群を使用して画像ファイルをHBITMAPとして読み込む。 + HBITMAP load_image(LPCSTR filename) + { + // 拡張子を取得する。 + LPCSTR dotext = PathFindExtensionA(filename); + + // 拡張子をサポートしているプラグインで読み込みを試みる。 + for (size_t i = 0; i < m_plugins.size(); ++i) + { + SusiePlugin *plugin = m_plugins[i]; + if (plugin) + { + if (plugin->is_dotext_supported(dotext)) + { + HBITMAP hbm = plugin->load_image(filename); + if (hbm) + return hbm; + } + } + } + + return NULL; // 失敗。 + } + + // フィルター文字列を取得する。 + std::string get_filter() const + { + std::string ret; + for (size_t i = 0; i < m_plugins.size(); ++i) + { + SusiePlugin *plugin = m_plugins[i]; + if (plugin && plugin->m_filter.size()) + { + if (ret.size()) + ret += ";"; + ret += plugin->m_filter; + } + } + return ret; + } +}; diff --git a/TempFile.hpp b/TempFile.hpp new file mode 100644 index 0000000..4bdd6c7 --- /dev/null +++ b/TempFile.hpp @@ -0,0 +1,115 @@ +// TempFile.hpp --- 一時ファイルを自動化する by katahiromz +#pragma once + +#include // 文字列を安全に扱う関数(StringCc*)。 +#include // assertマクロ。 + +// 一時ファイルを扱うクラス。 +class TempFile +{ +protected: + DWORD m_old_value; + TCHAR m_tempfile[MAX_PATH]; // ファイル名。 + TCHAR m_temppath[MAX_PATH]; // パスファイル名。 + TCHAR m_prefix[4]; // プレフィックス。 + TCHAR m_dot_ext[8]; // 拡張子(ドットで始まる)。 + + // ファイル名を多様にするための調味料。 + DWORD get_salt() + { + // 現在の時刻データを利用する。 + SYSTEMTIME st; + ::GetLocalTime(&st); + + DWORD dw = m_old_value; // 古い値も利用する。 + dw += st.wYear; + dw += st.wMonth; + dw += st.wDay; + dw += st.wHour; + dw += st.wMinute; + dw += st.wSecond; + dw += st.wMilliseconds; + m_old_value = dw; // 値を保存する。 + return LOWORD(dw) ^ HIWORD(dw); // 値は符号なし2バイト整数の範囲。 + } + +public: + // コンストラクタ。 + TempFile() : m_old_value(::GetTickCount()) + { + m_tempfile[0] = m_temppath[0] = m_prefix[0] = m_dot_ext[0] = 0; + } + + // コンストラクタ。 + TempFile(LPCTSTR prefix, LPCTSTR dot_ext) : m_old_value(::GetTickCount()) + { + init(prefix, dot_ext); + } + + // デストラクタ。 + ~TempFile() + { + // 自動的に削除する。 + erase(); + } + + // 初期化。第一引数はプレフィックス。第二引数は拡張子。 + void init(LPCTSTR prefix, LPCTSTR dot_ext) + { + m_tempfile[0] = 0; + ::GetTempPath(_countof(m_temppath), m_temppath); + StringCchCopy(m_prefix, _countof(m_prefix), prefix); + StringCchCopy(m_dot_ext, _countof(m_dot_ext), dot_ext); + assert(dot_ext[0] == TEXT('.') || dot_ext[0] == 0); + } + + // 一時ファイルを作成し、パスファイル名を返す。 + LPCTSTR make() + { + for (UINT i = 0; i < 1000; ++i) // 1000回やってダメならあきらめよう。 + { + // 調味料を使ってパスファイル名を作成する。 + StringCchPrintf(m_tempfile, _countof(m_tempfile), + TEXT("%s%s-%04lX%s"), + m_temppath, m_prefix, get_salt(), m_dot_ext); + // 新しく作成できたら、それを採用。 + HANDLE hFile = ::CreateFile(m_tempfile, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + ::CloseHandle(hFile); + return m_tempfile; + } + // 作成できなかったらやり直す。 + } + + // 作成失敗。 + assert(0); + m_tempfile[0] = 0; + return NULL; + } + + // パスファイル名を返す。 + LPCTSTR get() const + { + assert(m_tempfile[0] != 0); // make first! + + if (m_tempfile[0] != 0) + return m_tempfile; + + return NULL; + } + + // 一時ファイルを削除する。 + void erase() + { + if (m_tempfile[0]) + ::DeleteFile(m_tempfile); + } + + // 名前をクリアする。削除されてなければこのクラスは一時ファイルを削除しない。 + void clear() + { + m_tempfile[0] = 0; + } +}; diff --git a/color_value b/color_value new file mode 160000 index 0000000..6a5f563 --- /dev/null +++ b/color_value @@ -0,0 +1 @@ +Subproject commit 6a5f563c4bc588c518bb6cb66d94535900e12015 diff --git a/do_build.bat b/do_build.bat new file mode 100644 index 0000000..3ff7ff4 --- /dev/null +++ b/do_build.bat @@ -0,0 +1,4 @@ +del CMakeCache.txt +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS:BOOL=OFF . +ninja +strip build\GNPDF.exe diff --git a/fontmap.dat b/fontmap.dat new file mode 100644 index 0000000..4879a41 --- /dev/null +++ b/fontmap.dat @@ -0,0 +1,43 @@ +; 【ガゾーナラベPDFで利用するフォント情報】 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; このファイルは、自由に変更しても構いません。 + +[FONTMAP] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Windows標準フォント。 +; 利用条件:編集可能。 +MS ゴシック=MSGOTHIC.TTC,0 +MS Pゴシック=MSGOTHIC.TTC,2 +MS 明朝=MSMINCHO.TTC,0 +MS P明朝=MSMINCHO.TTC,1 +MS UI Gothic=MSGOTHIC.TTC,1 +;メイリオ=MEIRYO.TTC,0 ;「メイリオ」には行間の問題があるので読み込まない。 +Meiryo UI=MEIRYO.TTC,2 +游ゴシック=YUGOTHR.TTC,0 +游明朝=YUMIN.TTF +Yu Gothic UI=YUGOTHM.TTC,1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; BIZ UDPフォント。 +; 利用条件:印刷とプレビュー可能。 +BIZ UDゴシック=BIZ-UDGOTHICR.TTC,0 +BIZ UDPゴシック=BIZ-UDGOTHICR.TTC,1 +BIZ UD明朝=BIZ-UDMINCHOM.TTC,0 +BIZ UDP明朝=BIZ-UDMINCHOM.TTC,1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; RICOHフォント。 +; 利用条件:編集可能。 +HGゴシックE=HGRGE90.TTC,0 +HGPゴシックE=HGRGE90.TTC,1 +HGSゴシックE=HGRGE90.TTC,2 +HG明朝E=HGRME90.TTC,0 +HGP明朝E=HGRME90.TTC,1 +HGS明朝E=HGRME90.TTC,2 +HG丸ゴシックM-PRO=HGRSM90.TTF +HG創英角ゴシックUB=HGRSGU90.TTC,0 +HGP創英角ゴシックUB=HGRSGU90.TTC,1 +HGS創英角ゴシックUB=HGRSGU90.TTC,2 +HG創英角ポップ体=HGRPP190.TTC,0 +HGP創英角ポップ体=HGRPP190.TTC,1 +HGS創英角ポップ体=HGRPP190.TTC,2 diff --git a/gpimage.cpp b/gpimage.cpp new file mode 100644 index 0000000..438b771 --- /dev/null +++ b/gpimage.cpp @@ -0,0 +1,469 @@ +#include +#include +#include +#include +#pragma comment(lib, "gdiplus.lib") + +using namespace Gdiplus; + +// GDI+を使用するために必要なデータ。 +GdiplusStartupInput g_gdiplusStartupInput; +ULONG_PTR g_gdiplusToken = 0; + +// gpimageライブラリの初期化処理。 +BOOL gpimage_init(void) +{ + // GDI+の初期化。 + return GdiplusStartup(&g_gdiplusToken, &g_gdiplusStartupInput, NULL) == Ok; +} + +// gpimageライブラリの終了処理。 +void gpimage_exit(void) +{ + // GDI+ の終了処理。 + GdiplusShutdown(g_gdiplusToken); + g_gdiplusToken = 0; +} + +// ファイル名(拡張子を含む)からMIMEの種類を取得する。 +LPCWSTR gpimage_get_mime_from_filename(LPCWSTR filename) +{ + // 拡張子を取得する。 + LPWSTR dotext = PathFindExtensionW(filename); + if (!dotext || !*dotext) + return NULL; + + // JPEG + if (lstrcmpiW(dotext, L".jpg") == 0 || + lstrcmpiW(dotext, L".jpeg") == 0 || + lstrcmpiW(dotext, L".jpe") == 0 || + lstrcmpiW(dotext, L".jfif") == 0) + { + return L"image/jpeg"; + } + + // PNG + if (lstrcmpiW(dotext, L".png") == 0) + { + return L"image/png"; + } + + // GIF + if (lstrcmpiW(dotext, L".gif") == 0) + { + return L"image/gif"; + } + + // BMP + if (lstrcmpiW(dotext, L".bmp") == 0 || lstrcmpiW(dotext, L".dib") == 0) + { + return L"image/bmp"; + } + + // TIFF + if (lstrcmpiW(dotext, L".tif") == 0 || lstrcmpiW(dotext, L".tiff") == 0) + { + return L"image/tiff"; + } + + // WMF + if (lstrcmpiW(dotext, L".wmf") == 0) + { + return L"image/x-wmf"; + } + + // EMF + if (lstrcmpiW(dotext, L".emf") == 0) + { + return L"image/x-emf"; + } + + // ICO + if (lstrcmpiW(dotext, L".ico") == 0) + { + return L"image/vnd.microsoft.icon"; + } + + // CUR + if (lstrcmpiW(dotext, L".cur") == 0) + { + return L"application/octet-stream"; + } + + return NULL; // 失敗。 +} + +// 拡張子が有効かをチェックする。 +BOOL gpimage_is_valid_extension(LPCWSTR filename) +{ + return gpimage_get_mime_from_filename(filename) != NULL; +} + +// ファイル名の拡張子からエンコーダーのCLSIDを取得する。 +BOOL gpimage_get_encoder_from_filename(LPCWSTR filename, CLSID *pClsid) +{ + *pClsid = GUID_NULL; + + LPCWSTR mime = gpimage_get_mime_from_filename(filename); + if (mime == NULL) + return FALSE; + + // フォーマットを指定して、エンコーダーの GUID を取得する + UINT num = 0, size = 0; + GetImageEncodersSize(&num, &size); + if (size == 0) + return FALSE; + + ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)malloc(size); + if (pImageCodecInfo == NULL) + { + return FALSE; + } + + GetImageEncoders(num, size, pImageCodecInfo); + for (UINT j = 0; j < num; ++j) + { + if (lstrcmpiW(pImageCodecInfo[j].MimeType, mime) == 0) + { + free(pImageCodecInfo); + *pClsid = pImageCodecInfo[j].Clsid; + return TRUE; + } + } + + free(pImageCodecInfo); + return FALSE; +} + +// ファイルの日付を取得する(撮影日時を除く)。 +BOOL gpimage_load_datetime(LPCWSTR filename, FILETIME* pftCreated, FILETIME* pftModified) +{ + if (pftCreated) + ZeroMemory(pftCreated, sizeof(*pftCreated)); + if (pftModified) + ZeroMemory(pftModified, sizeof(*pftModified)); + + // ファイルの日時を読み込む。 + WIN32_FIND_DATA find; + HANDLE hFind = FindFirstFile(filename, &find); + if (hFind != INVALID_HANDLE_VALUE) + { + FindClose(hFind); + // ファイル作成日時を取得する。 + if (pftCreated) + ::FileTimeToLocalFileTime(&find.ftCreationTime, pftCreated); + // ファイル更新日時を取得する。 + if (pftModified) + ::FileTimeToLocalFileTime(&find.ftLastWriteTime, pftModified); + return TRUE; + } + + return FALSE; +} + +// カーソルファイル(*.cur)をHBITMAPとして読み込む。 +HBITMAP gpimage_load_cursor(LPCWSTR filename, int* width, int* height, FILETIME* pftCreated, FILETIME* pftModified) +{ + // 初期化する。 + if (width) + *width = 0; + if (height) + *height = 0; + if (pftCreated) + ZeroMemory(pftCreated, sizeof(*pftCreated)); + if (pftModified) + ZeroMemory(pftModified, sizeof(*pftModified)); + + // カーソルファイルを読み込む。 + HCURSOR hCursor = ::LoadCursorFromFile(filename); + if (hCursor == NULL) + { + return NULL; + } + + // カーソルの情報を取得する。 + ICONINFO ii; + if (!::GetIconInfo((HICON)hCursor, &ii)) + { + ::DestroyCursor(hCursor); + return NULL; + } + + // ii.hbmColorから情報を取得する。 + BITMAP bm; + GetObject(ii.hbmMask, sizeof(bm), &bm); + bm.bmHeight /= 2; // 2倍の高さを1倍に。 + if (width) + *width = bm.bmWidth; + if (height) + *height = bm.bmHeight; + + // ビットマップを破棄する。 + DeleteObject(ii.hbmMask); + DeleteObject(ii.hbmColor); + + // 互換DCを作成する。 + HDC hdc = CreateCompatibleDC(NULL); + + // 24BPPのビットマップオブジェクトを作成する。 + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = bm.bmWidth; + bmi.bmiHeader.biHeight = bm.bmHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; + HBITMAP hbm = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + if (hbm) + { + // ビットマップを選択する。 + HGDIOBJ hbmOld = SelectObject(hdc, hbm); + + // カーソルを描画する。 + DrawIconEx(hdc, 0, 0, (HICON)hCursor, bm.bmWidth, bm.bmHeight, 0, GetStockBrush(WHITE_BRUSH), DI_NORMAL); + + // ビットマップの選択を解除する。 + SelectObject(hdc, hbmOld); + } + + // ファイルの日時を読み込む。 + gpimage_load_datetime(filename, pftCreated, pftModified); + + // DCを破棄する。 + DeleteDC(hdc); + + return hbm; +} + +// 画像をHBITMAPとして読み込む。 +HBITMAP gpimage_load(LPCWSTR filename, int* width, int* height, FILETIME* pftCreated, FILETIME* pftModified) +{ + // カーソルの拡張子ならばカーソルとして読み込む。 + if (lstrcmpiW(PathFindExtensionW(filename), L".cur") == 0) + { + return gpimage_load_cursor(filename, width, height, pftCreated, pftModified); + } + + HBITMAP hBitmap = NULL; + + // 画像を読み込む + if (Image *image = Image::FromFile(filename)) + { + // 画像の回転角度を取得する + int orientation = 0; + { + PropertyItem *propertyItem; + UINT size = image->GetPropertyItemSize(PropertyTagOrientation); + if (size) + { + if (PropertyItem* propertyItem = (PropertyItem*)malloc(size)) + { + image->GetPropertyItem(PropertyTagOrientation, size, propertyItem); + orientation = *(int*)propertyItem->value; + free(propertyItem); + } + } + } + + // 画像の撮影日時または作成日時を取得する。 + if (pftCreated) + { + ZeroMemory(pftCreated, sizeof(*pftCreated)); + BOOL found = FALSE; + + // EXIFから撮影日時の取得を試みる。 + UINT size = image->GetPropertyItemSize(PropertyTagExifDTOrig); + if (PropertyItem* propertyItem = (PropertyItem*)malloc(size)) + { + image->GetPropertyItem(PropertyTagExifDTOrig, size, propertyItem); + if (propertyItem->type == PropertyTagTypeASCII) + { + // 「YYYY:MM:DD HH:MM:SS」 + auto datetime = (const char*)propertyItem->value; + SYSTEMTIME st; + st.wYear = (WORD)atoi(&datetime[0]); + st.wMonth = (WORD)atoi(&datetime[5]); + st.wDay = (WORD)atoi(&datetime[8]); + st.wHour = (WORD)atoi(&datetime[11]); + st.wMinute = (WORD)atoi(&datetime[14]); + st.wSecond = (WORD)atoi(&datetime[17]); + st.wMilliseconds = 0; + if (::SystemTimeToFileTime(&st, pftCreated)) + found = TRUE; + } + free(propertyItem); + } + + // 取得できなければ、ファイル作成日時を取得する。 + if (!found) + { + WIN32_FIND_DATAW find; + HANDLE hFind = FindFirstFileW(filename, &find); + if (hFind != INVALID_HANDLE_VALUE) + { + FindClose(hFind); + ::FileTimeToLocalFileTime(&find.ftCreationTime, pftCreated); + } + } + } + + // ファイル更新日時を取得する。 + if (pftModified) + { + ZeroMemory(pftModified, sizeof(*pftModified)); + WIN32_FIND_DATAW find; + HANDLE hFind = ::FindFirstFileW(filename, &find); + if (hFind != INVALID_HANDLE_VALUE) + { + ::FindClose(hFind); + ::FileTimeToLocalFileTime(&find.ftLastWriteTime, pftModified); + } + } + + // 画像を回転する + switch (orientation) + { + case 1: // 回転無し + break; + case 2: // 左右反転 + image->RotateFlip(RotateNoneFlipX); + break; + case 3: // 180度回転 + image->RotateFlip(Rotate180FlipNone); + break; + case 4: // 上下反転 + image->RotateFlip(RotateNoneFlipY); + break; + case 5: // 左右反転 + 90度回転 + image->RotateFlip(Rotate90FlipX); + break; + case 6: // 90度回転 + image->RotateFlip(Rotate90FlipNone); + break; + case 7: // 左右反転 + 270度回転 + image->RotateFlip(Rotate270FlipX); + break; + case 8: // 270度回転 + image->RotateFlip(Rotate270FlipNone); + break; + } + //// 回転角度プロパティを削除する。 + //image->RemovePropertyItem(PropertyTagOrientation); + + // 画像のサイズを取得する + int cx = image->GetWidth(); + int cy = image->GetHeight(); + + if (width) + *width = cx; + if (height) + *height = cy; + + // HBITMAPを作成する + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cx; + bmi.bmiHeader.biHeight = -cy; // 縦方向を反転する + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; + hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + if (hBitmap) + { + // 画像をHBITMAPに描画する + if (HDC hdcMem = CreateCompatibleDC(NULL)) + { + HGDIOBJ hbmOld = SelectObject(hdcMem, hBitmap); + { + RECT rc = { 0, 0, cx, cy }; + FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH)); + Graphics graphics(hdcMem); + graphics.DrawImage(image, 0, 0, cx, cy); + } + SelectObject(hdcMem, hbmOld); + DeleteDC(hdcMem); + } + else + { + DeleteObject(hBitmap); + hBitmap = NULL; + } + } + + delete image; + } + + return hBitmap; +} + +// HBITMAPを画像ファイルとして保存する。 +BOOL gpimage_save(LPCWSTR filename, HBITMAP hBitmap) +{ + BOOL ret = FALSE; + + CLSID clsid; + if (!gpimage_get_encoder_from_filename(filename, &clsid)) + return FALSE; + + // HBITMAP を GDI+ の Bitmap に変換する + if (Bitmap* pBitmap = Bitmap::FromHBITMAP(hBitmap, NULL)) + { + // 保存する + if (pBitmap->Save(filename, &clsid, NULL) == Ok) + { + ret = TRUE; + } + + // 破棄する。 + delete pBitmap; + } + + return ret; +} + +// HBITMAPを拡大縮小する。 +HBITMAP gpimage_resize(HBITMAP hbmSrc, int width, int height) +{ + BITMAP bm; + if (!GetObject(hbmSrc, sizeof(bm), &bm)) + return NULL; + + // DCを作成する。 + HDC hdcSrc = CreateCompatibleDC(NULL); + if (!hdcSrc) + return NULL; + HDC hdcDest = CreateCompatibleDC(NULL); + if (!hdcDest) + { + DeleteDC(hdcSrc); + return NULL; + } + + // HBITMAPを作成する + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; // 縦方向を反転する + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; + HBITMAP hbmDest = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + if (hbmDest) + { + // ビットマップを選択。 + HGDIOBJ hbmSrcOld = SelectObject(hdcSrc, hbmSrc); + HGDIOBJ hbmDestOld = SelectObject(hdcDest, hbmDest); + // なめらかに拡大縮小する。 + SetStretchBltMode(hdcDest, STRETCH_HALFTONE); + StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); + // ビットマップの選択を解除。 + SelectObject(hdcSrc, hbmSrcOld); + SelectObject(hdcDest, hbmDestOld); + } + + // DCを破棄する。 + DeleteDC(hdcDest); + DeleteDC(hdcSrc); + + return hbmDest; +} diff --git a/gpimage.hpp b/gpimage.hpp new file mode 100644 index 0000000..a5fc2f4 --- /dev/null +++ b/gpimage.hpp @@ -0,0 +1,21 @@ +#pragma once + +// gpimageライブラリの初期化処理。 +BOOL gpimage_init(void); +// gpimageライブラリの終了処理。 +void gpimage_exit(void); +// ファイル名(拡張子を含む)からMIMEの種類を取得する。 +LPCWSTR gpimage_get_mime_from_filename(LPCWSTR filename); +// 拡張子が有効かをチェックする。 +BOOL gpimage_is_valid_extension(LPCWSTR filename); +// ファイル名の拡張子からエンコーダーのCLSIDを取得する。 +BOOL gpimage_get_encoder_from_filename(LPCWSTR filename, CLSID *pClsid); +// 画像をHBITMAPとして読み込む。 +HBITMAP gpimage_load(LPCWSTR filename, int* width = NULL, int* height = NULL, + FILETIME* pftCreated = NULL, FILETIME* pftModified = NULL); +// HBITMAPを画像ファイルとして保存する。 +BOOL gpimage_save(LPCWSTR filename, HBITMAP hBitmap); +// HBITMAPを拡大縮小する。 +HBITMAP gpimage_resize(HBITMAP hbmSrc, int width, int height); +// ファイルの日付を取得する(撮影日時を除く)。 +BOOL gpimage_load_datetime(LPCWSTR filename, FILETIME* pftCreated = NULL, FILETIME* pftModified = NULL); diff --git a/hand.cur b/hand.cur new file mode 100644 index 0000000000000000000000000000000000000000..3b88dbdf3ae41cf629308dab78c27502f460c87f GIT binary patch literal 326 zcma*hAr1mD5QX7aDpnwH7z8VkBuE4tg#*ADTFJ3E3P(V$C_Mfa46J}@zIn-{(^Q%$ zsPCnYTnA|`X$3~wBXDZv*2m5Ec=%%y9#X7W?VOt#ImH57Z0=d{RB`W!19Dr8`)VReJZw}k?46@ literal 0 HcmV?d00001 diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..1d970b0 --- /dev/null +++ b/installer.iss @@ -0,0 +1,69 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "fW^WO\[[J[" +#define MyAppVersion "0.0.0" +#define MyAppPublisher "ЎRMZ" +#define MyAppURL "https://katahiromz.web.fc2.com/" +#define MyAppExeName "Jigsaw.exe" +#define MyAppCopyright "(c) katahiromz" +#define MyAppDescription "摜ׂPDF쐬" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{38720DA3-2036-4103-A89C-62A8C5FC057D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={pf}\DigitalJigsawMaker +DefaultGroupName={#MyAppName} +LicenseFile=LICENSE.txt +OutputDir=build +OutputBaseFilename=Jigsaw-{#MyAppVersion}-setup +SetupIconFile=res\Icon_1.ico +Compression=lzma +SolidCompression=yes +UninstallDisplayIcon={app}\{#MyAppExeName} +VersionInfoCompany={#MyAppPublisher} +VersionInfoCopyright={#MyAppCopyright} +VersionInfoDescription={#MyAppDescription} +VersionInfoProductName={#MyAppName} +VersionInfoProductTextVersion={#MyAppVersion} +VersionInfoProductVersion={#MyAppVersion} +VersionInfoVersion={#MyAppVersion} + +[Languages] +;Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" + +[Files] +Source: "build\Jigsaw.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "README.txt"; DestDir: "{app}"; Flags: ignoreversion +Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion +Source: "HISTORY.txt"; DestDir: "{app}"; Flags: ignoreversion +Source: "TAGS.txt"; DestDir: "{app}"; Flags: ignoreversion +Source: "fontmap.dat"; DestDir: "{app}"; Flags: ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{group}\README.txt"; Filename: "{app}\README.txt" +Name: "{group}\LICENSE.txt"; Filename: "{app}\LICENSE.txt" +Name: "{group}\HISTORY.txt"; Filename: "{app}\HISTORY.txt" +Name: "{group}\ACXg["; Filename: "{uninstallexe}" +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Registry] +Root: HKCU; Subkey: "Software\Katayama Hirofumi MZ\DigitalJigsawMaker"; Flags: uninsdeletekey diff --git a/libharu b/libharu new file mode 160000 index 0000000..8dbcfe4 --- /dev/null +++ b/libharu @@ -0,0 +1 @@ +Subproject commit 8dbcfe470581edc4bc52c3ef132d410e66cadc20 diff --git a/res/Manifest_1.manifest b/res/Manifest_1.manifest new file mode 100644 index 0000000..bd28d82 --- /dev/null +++ b/res/Manifest_1.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..ba7b92a --- /dev/null +++ b/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ Compatible +// This file is automatically generated by RisohEditor. +// Jigsaw_res.rc + + +#ifdef APSTUDIO_INVOKED + #ifndef APSTUDIO_READONLY_SYMBOLS + #define _APS_NO_MFC 1 + #define _APS_NEXT_RESOURCE_VALUE 100 + #define _APS_NEXT_COMMAND_VALUE 100 + #define _APS_NEXT_CONTROL_VALUE 1000 + #define _APS_NEXT_SYMED_VALUE 300 + #endif +#endif