#include "legacy_asm_port.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr wchar_t kWindowClass[] = L"OpusWordX64Window"; constexpr wchar_t kProductName[] = L"Microsoft Word x64 Port"; constexpr UINT_PTR kStatusTimer = 1; enum Command : UINT { cmd_file_new = 100, cmd_file_open, cmd_file_save, cmd_file_save_as, cmd_file_print, cmd_file_exit, cmd_edit_undo = 200, cmd_edit_redo, cmd_edit_cut, cmd_edit_copy, cmd_edit_paste, cmd_edit_select_all, cmd_edit_find, cmd_edit_find_next, cmd_format_bold = 300, cmd_format_italic, cmd_format_underline, cmd_format_font, cmd_format_left, cmd_format_center, cmd_format_right, cmd_format_justify, cmd_tools_word_count = 400, cmd_help_about = 500, }; [[nodiscard]] std::wstring system_error_message(const DWORD error) { wchar_t* buffer = nullptr; const DWORD count = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error, 0, reinterpret_cast(&buffer), 0, nullptr); std::wstring message = count != 0 && buffer != nullptr ? std::wstring(buffer, count) : L"Unknown error (" + std::to_wstring(error) + L")"; if (buffer != nullptr) { LocalFree(buffer); } while (!message.empty() && (message.back() == L'\r' || message.back() == L'\n')) { message.pop_back(); } return message; } [[nodiscard]] std::wstring lower_extension(const std::wstring& path) { auto extension = std::filesystem::path(path).extension().wstring(); std::ranges::transform(extension, extension.begin(), [](const wchar_t ch) { return static_cast(std::towlower(ch)); }); return extension; } [[nodiscard]] std::vector read_file(const std::wstring& path) { const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError())); } LARGE_INTEGER size{}; if (!GetFileSizeEx(file, &size) || size.QuadPart < 0 || size.QuadPart > static_cast(std::numeric_limits::max())) { const DWORD error = GetLastError(); CloseHandle(file); throw std::runtime_error("GetFileSizeEx failed: " + std::to_string(error)); } std::vector bytes(static_cast(size.QuadPart)); DWORD read = 0; const BOOL succeeded = bytes.empty() || ReadFile(file, bytes.data(), static_cast(bytes.size()), &read, nullptr); const DWORD error = succeeded ? ERROR_SUCCESS : GetLastError(); CloseHandle(file); if (!succeeded || read != bytes.size()) { throw std::runtime_error("ReadFile failed: " + std::to_string(error)); } return bytes; } void write_file(const std::wstring& path, const std::vector& bytes) { const HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError())); } DWORD written = 0; const BOOL succeeded = bytes.empty() || WriteFile(file, bytes.data(), static_cast(bytes.size()), &written, nullptr); const DWORD error = succeeded ? ERROR_SUCCESS : GetLastError(); CloseHandle(file); if (!succeeded || written != bytes.size()) { throw std::runtime_error("WriteFile failed: " + std::to_string(error)); } } [[nodiscard]] std::wstring decode_text(const std::vector& bytes) { if (bytes.size() >= 2 && bytes[0] == std::byte{0xff} && bytes[1] == std::byte{0xfe}) { std::wstring result((bytes.size() - 2) / sizeof(wchar_t), L'\0'); std::memcpy(result.data(), bytes.data() + 2, result.size() * sizeof(wchar_t)); return result; } std::size_t offset = 0; if (bytes.size() >= 3 && bytes[0] == std::byte{0xef} && bytes[1] == std::byte{0xbb} && bytes[2] == std::byte{0xbf}) { offset = 3; } const auto* text = reinterpret_cast(bytes.data() + offset); const int byte_count = static_cast(bytes.size() - offset); if (byte_count == 0) { return {}; } UINT code_page = CP_UTF8; DWORD flags = MB_ERR_INVALID_CHARS; int character_count = MultiByteToWideChar(code_page, flags, text, byte_count, nullptr, 0); if (character_count == 0) { code_page = CP_ACP; flags = 0; character_count = MultiByteToWideChar(code_page, flags, text, byte_count, nullptr, 0); } if (character_count == 0) { throw std::runtime_error("The text encoding could not be decoded"); } std::wstring result(static_cast(character_count), L'\0'); MultiByteToWideChar(code_page, flags, text, byte_count, result.data(), character_count); return result; } [[nodiscard]] std::vector encode_utf8(const std::wstring_view text) { const int byte_count = WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast(text.size()), nullptr, 0, nullptr, nullptr); std::vector result{ std::byte{0xef}, std::byte{0xbb}, std::byte{0xbf}, }; const auto initial_size = result.size(); result.resize(initial_size + static_cast(byte_count)); if (byte_count != 0) { WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast(text.size()), reinterpret_cast(result.data() + initial_size), byte_count, nullptr, nullptr); } return result; } [[nodiscard]] HMENU build_menu() { const HMENU menu = CreateMenu(); const HMENU file = CreatePopupMenu(); AppendMenuW(file, MF_STRING, cmd_file_new, L"&New\tCtrl+N"); AppendMenuW(file, MF_STRING, cmd_file_open, L"&Open...\tCtrl+O"); AppendMenuW(file, MF_STRING, cmd_file_save, L"&Save\tCtrl+S"); AppendMenuW(file, MF_STRING, cmd_file_save_as, L"Save &As..."); AppendMenuW(file, MF_SEPARATOR, 0, nullptr); AppendMenuW(file, MF_STRING, cmd_file_print, L"&Print...\tCtrl+P"); AppendMenuW(file, MF_SEPARATOR, 0, nullptr); AppendMenuW(file, MF_STRING, cmd_file_exit, L"E&xit"); AppendMenuW(menu, MF_POPUP, reinterpret_cast(file), L"&File"); const HMENU edit = CreatePopupMenu(); AppendMenuW(edit, MF_STRING, cmd_edit_undo, L"&Undo\tCtrl+Z"); AppendMenuW(edit, MF_STRING, cmd_edit_redo, L"&Redo\tCtrl+Y"); AppendMenuW(edit, MF_SEPARATOR, 0, nullptr); AppendMenuW(edit, MF_STRING, cmd_edit_cut, L"Cu&t\tCtrl+X"); AppendMenuW(edit, MF_STRING, cmd_edit_copy, L"&Copy\tCtrl+C"); AppendMenuW(edit, MF_STRING, cmd_edit_paste, L"&Paste\tCtrl+V"); AppendMenuW(edit, MF_STRING, cmd_edit_select_all, L"Select &All\tCtrl+A"); AppendMenuW(edit, MF_SEPARATOR, 0, nullptr); AppendMenuW(edit, MF_STRING, cmd_edit_find, L"&Find...\tCtrl+F"); AppendMenuW(edit, MF_STRING, cmd_edit_find_next, L"Find &Next\tF3"); AppendMenuW(menu, MF_POPUP, reinterpret_cast(edit), L"&Edit"); const HMENU format = CreatePopupMenu(); AppendMenuW(format, MF_STRING, cmd_format_bold, L"&Bold\tCtrl+B"); AppendMenuW(format, MF_STRING, cmd_format_italic, L"&Italic\tCtrl+I"); AppendMenuW(format, MF_STRING, cmd_format_underline, L"&Underline\tCtrl+U"); AppendMenuW(format, MF_STRING, cmd_format_font, L"&Font..."); AppendMenuW(format, MF_SEPARATOR, 0, nullptr); AppendMenuW(format, MF_STRING, cmd_format_left, L"Align &Left\tCtrl+L"); AppendMenuW(format, MF_STRING, cmd_format_center, L"&Center\tCtrl+E"); AppendMenuW(format, MF_STRING, cmd_format_right, L"Align &Right\tCtrl+R"); AppendMenuW(format, MF_STRING, cmd_format_justify, L"&Justify\tCtrl+J"); AppendMenuW(menu, MF_POPUP, reinterpret_cast(format), L"F&ormat"); const HMENU tools = CreatePopupMenu(); AppendMenuW(tools, MF_STRING, cmd_tools_word_count, L"&Word Count..."); AppendMenuW(menu, MF_POPUP, reinterpret_cast(tools), L"&Tools"); const HMENU help = CreatePopupMenu(); AppendMenuW(help, MF_STRING, cmd_help_about, L"&About Microsoft Word x64 Port"); AppendMenuW(menu, MF_POPUP, reinterpret_cast(help), L"&Help"); return menu; } class WordApplication { public: explicit WordApplication(const HINSTANCE instance) : instance_(instance) {} int run(const int show_command) { int argument_count = 0; wchar_t** arguments = CommandLineToArgvW(GetCommandLineW(), &argument_count); if (arguments != nullptr && argument_count > 1 && std::wstring_view(arguments[1]) == L"--self-test") { std::array pattern_buffer{}; constexpr std::array pattern{1, 2, 3}; opus::x64::fill_byte_pattern(pattern_buffer.data(), pattern_buffer.size(), pattern.data(), pattern.size()); const bool passed = opus::x64::assembly_manifest_complete() && opus::x64::assembly_manifest_count() == 59 && opus::x64::multiply_divide(10, 3, 2) == 15 && opus::x64::legacy_square(4.0) == 16.0 && pattern_buffer == std::array{1, 2, 3, 1, 2, 3, 1} && opus::x64::is_ascii_alphanumeric('Z') && opus::x64::ascii_upper('q') == 'Q'; LocalFree(arguments); return passed ? 0 : 2; } SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_BAR_CLASSES | ICC_STANDARD_CLASSES}; InitCommonControlsEx(&controls); rich_edit_library_ = LoadLibraryW(L"Msftedit.dll"); if (rich_edit_library_ == nullptr) { show_error(L"Microsoft Rich Edit could not be loaded", GetLastError()); if (arguments != nullptr) { LocalFree(arguments); } return 1; } WNDCLASSEXW window_class{sizeof(window_class)}; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.lpfnWndProc = &WordApplication::window_proc; window_class.hInstance = instance_; window_class.hIcon = LoadIconW(nullptr, IDI_APPLICATION); window_class.hCursor = LoadCursorW(nullptr, IDC_ARROW); window_class.hbrBackground = reinterpret_cast(COLOR_APPWORKSPACE + 1); window_class.lpszClassName = kWindowClass; window_class.hIconSm = window_class.hIcon; if (RegisterClassExW(&window_class) == 0) { show_error(L"The application window could not be registered", GetLastError()); FreeLibrary(rich_edit_library_); if (arguments != nullptr) { LocalFree(arguments); } return 1; } main_window_ = CreateWindowExW( 0, kWindowClass, kProductName, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 1100, 800, nullptr, nullptr, instance_, this); if (main_window_ == nullptr) { show_error(L"The application window could not be created", GetLastError()); FreeLibrary(rich_edit_library_); if (arguments != nullptr) { LocalFree(arguments); } return 1; } if (arguments != nullptr && argument_count > 1 && std::wstring_view(arguments[1]) == L"--ui-self-test") { SetWindowTextW(editor_, L"Word x64 UI smoke test"); const bool passed = IsWindow(main_window_) != FALSE && IsWindow(editor_) != FALSE && IsWindow(status_) != FALSE && GetWindowTextLengthW(editor_) == 22; DestroyWindow(main_window_); LocalFree(arguments); FreeLibrary(rich_edit_library_); return passed ? 0 : 3; } if (arguments != nullptr && argument_count > 1) { open_path(arguments[1]); } if (arguments != nullptr) { LocalFree(arguments); } ShowWindow(main_window_, show_command); UpdateWindow(main_window_); const std::array accelerators{ ACCEL{FVIRTKEY | FCONTROL, 'N', cmd_file_new}, ACCEL{FVIRTKEY | FCONTROL, 'O', cmd_file_open}, ACCEL{FVIRTKEY | FCONTROL, 'S', cmd_file_save}, ACCEL{FVIRTKEY | FCONTROL, 'P', cmd_file_print}, ACCEL{FVIRTKEY | FCONTROL, 'Z', cmd_edit_undo}, ACCEL{FVIRTKEY | FCONTROL, 'Y', cmd_edit_redo}, ACCEL{FVIRTKEY | FCONTROL, 'X', cmd_edit_cut}, ACCEL{FVIRTKEY | FCONTROL, 'C', cmd_edit_copy}, ACCEL{FVIRTKEY | FCONTROL, 'V', cmd_edit_paste}, ACCEL{FVIRTKEY | FCONTROL, 'A', cmd_edit_select_all}, ACCEL{FVIRTKEY | FCONTROL, 'F', cmd_edit_find}, ACCEL{FVIRTKEY, VK_F3, cmd_edit_find_next}, ACCEL{FVIRTKEY | FCONTROL, 'B', cmd_format_bold}, ACCEL{FVIRTKEY | FCONTROL, 'I', cmd_format_italic}, ACCEL{FVIRTKEY | FCONTROL, 'U', cmd_format_underline}, ACCEL{FVIRTKEY | FCONTROL, 'L', cmd_format_left}, ACCEL{FVIRTKEY | FCONTROL, 'E', cmd_format_center}, ACCEL{FVIRTKEY | FCONTROL, 'R', cmd_format_right}, ACCEL{FVIRTKEY | FCONTROL, 'J', cmd_format_justify}, }; const HACCEL accelerator_table = CreateAcceleratorTableW( const_cast(accelerators.data()), static_cast(accelerators.size())); MSG message{}; while (GetMessageW(&message, nullptr, 0, 0) > 0) { if (find_dialog_ != nullptr && IsDialogMessageW(find_dialog_, &message)) { continue; } if (!TranslateAcceleratorW(main_window_, accelerator_table, &message)) { TranslateMessage(&message); DispatchMessageW(&message); } } DestroyAcceleratorTable(accelerator_table); FreeLibrary(rich_edit_library_); return static_cast(message.wParam); } private: static LRESULT CALLBACK window_proc(const HWND window, const UINT message, const WPARAM wparam, const LPARAM lparam) { WordApplication* app = reinterpret_cast( GetWindowLongPtrW(window, GWLP_USERDATA)); if (message == WM_NCCREATE) { const auto* create = reinterpret_cast(lparam); app = static_cast(create->lpCreateParams); app->main_window_ = window; SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast(app)); } return app != nullptr ? app->dispatch(message, wparam, lparam) : DefWindowProcW(window, message, wparam, lparam); } LRESULT dispatch(const UINT message, const WPARAM wparam, const LPARAM lparam) { if (message == find_message_) { on_find_message(*reinterpret_cast(lparam)); return 0; } switch (message) { case WM_CREATE: return create_children() ? 0 : -1; case WM_SIZE: layout(LOWORD(lparam), HIWORD(lparam)); return 0; case WM_SETFOCUS: SetFocus(editor_); return 0; case WM_COMMAND: if (reinterpret_cast(lparam) == editor_ && HIWORD(wparam) == EN_CHANGE) { status_dirty_ = true; update_title(); return 0; } execute_command(LOWORD(wparam)); return 0; case WM_NOTIFY: if (reinterpret_cast(lparam)->hwndFrom == editor_) { status_dirty_ = true; } return 0; case WM_INITMENU: update_menu(reinterpret_cast(wparam)); return 0; case WM_TIMER: if (wparam == kStatusTimer && status_dirty_) { update_status(); } return 0; case WM_DROPFILES: open_drop(reinterpret_cast(wparam)); return 0; case WM_CLOSE: if (prompt_save_changes()) { DestroyWindow(main_window_); } return 0; case WM_DESTROY: KillTimer(main_window_, kStatusTimer); PostQuitMessage(0); return 0; default: return DefWindowProcW(main_window_, message, wparam, lparam); } } bool create_children() { SetMenu(main_window_, build_menu()); editor_ = CreateWindowExW( WS_EX_CLIENTEDGE, MSFTEDIT_CLASS, L"", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN | ES_NOHIDESEL | ES_AUTOVSCROLL, 0, 0, 0, 0, main_window_, reinterpret_cast(1), instance_, nullptr); status_ = CreateWindowExW(0, STATUSCLASSNAMEW, nullptr, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, main_window_, reinterpret_cast(2), instance_, nullptr); if (editor_ == nullptr || status_ == nullptr) { return false; } SendMessageW(editor_, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_SELCHANGE); SendMessageW(editor_, EM_SETBKGNDCOLOR, 0, RGB(255, 255, 255)); SendMessageW(editor_, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(16, 16)); SendMessageW(editor_, EM_EXLIMITTEXT, 0, std::numeric_limits::max()); CHARFORMAT2W format{sizeof(format)}; format.dwMask = CFM_FACE | CFM_SIZE | CFM_CHARSET; format.yHeight = 220; format.bCharSet = DEFAULT_CHARSET; wcscpy_s(format.szFaceName, L"Segoe UI"); SendMessageW(editor_, EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast(&format)); find_message_ = RegisterWindowMessageW(FINDMSGSTRINGW); DragAcceptFiles(main_window_, TRUE); SetTimer(main_window_, kStatusTimer, 250, nullptr); set_clean(); update_status(); SetFocus(editor_); return true; } void layout(const int width, const int height) { SendMessageW(status_, WM_SIZE, 0, 0); RECT status_rect{}; GetWindowRect(status_, &status_rect); const int status_height = status_rect.bottom - status_rect.top; MoveWindow(editor_, 0, 0, width, std::max(0, height - status_height), TRUE); const int parts[]{std::max(200, width - 330), std::max(320, width - 150), -1}; SendMessageW(status_, SB_SETPARTS, std::size(parts), reinterpret_cast(parts)); status_dirty_ = true; } void execute_command(const UINT command) { switch (command) { case cmd_file_new: new_document(); break; case cmd_file_open: open_dialog(); break; case cmd_file_save: save(false); break; case cmd_file_save_as: save(true); break; case cmd_file_print: print_document(); break; case cmd_file_exit: SendMessageW(main_window_, WM_CLOSE, 0, 0); break; case cmd_edit_undo: SendMessageW(editor_, EM_UNDO, 0, 0); break; case cmd_edit_redo: SendMessageW(editor_, EM_REDO, 0, 0); break; case cmd_edit_cut: SendMessageW(editor_, WM_CUT, 0, 0); break; case cmd_edit_copy: SendMessageW(editor_, WM_COPY, 0, 0); break; case cmd_edit_paste: SendMessageW(editor_, WM_PASTE, 0, 0); break; case cmd_edit_select_all: SendMessageW(editor_, EM_SETSEL, 0, -1); break; case cmd_edit_find: show_find(); break; case cmd_edit_find_next: find_next(find_flags_); break; case cmd_format_bold: toggle_effect(CFM_BOLD, CFE_BOLD); break; case cmd_format_italic: toggle_effect(CFM_ITALIC, CFE_ITALIC); break; case cmd_format_underline: toggle_effect(CFM_UNDERLINE, CFE_UNDERLINE); break; case cmd_format_font: choose_font(); break; case cmd_format_left: set_alignment(PFA_LEFT); break; case cmd_format_center: set_alignment(PFA_CENTER); break; case cmd_format_right: set_alignment(PFA_RIGHT); break; case cmd_format_justify: set_alignment(PFA_JUSTIFY); break; case cmd_tools_word_count: show_word_count(); break; case cmd_help_about: show_about(); break; default: break; } status_dirty_ = true; } void update_menu(const HMENU menu) const { const bool can_undo = SendMessageW(editor_, EM_CANUNDO, 0, 0) != 0; const bool can_redo = SendMessageW(editor_, EM_CANREDO, 0, 0) != 0; CHARRANGE selection{}; SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast(&selection)); EnableMenuItem(menu, cmd_edit_undo, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED)); EnableMenuItem(menu, cmd_edit_redo, MF_BYCOMMAND | (can_redo ? MF_ENABLED : MF_GRAYED)); EnableMenuItem(menu, cmd_edit_cut, MF_BYCOMMAND | (selection.cpMin != selection.cpMax ? MF_ENABLED : MF_GRAYED)); EnableMenuItem(menu, cmd_edit_copy, MF_BYCOMMAND | (selection.cpMin != selection.cpMax ? MF_ENABLED : MF_GRAYED)); EnableMenuItem(menu, cmd_edit_paste, MF_BYCOMMAND | (SendMessageW(editor_, EM_CANPASTE, 0, 0) ? MF_ENABLED : MF_GRAYED)); } void new_document() { if (!prompt_save_changes()) { return; } loading_ = true; SetWindowTextW(editor_, L""); current_path_.clear(); loading_ = false; set_clean(); } void open_dialog() { if (!prompt_save_changes()) { return; } std::array path{}; OPENFILENAMEW dialog{sizeof(dialog)}; dialog.hwndOwner = main_window_; dialog.lpstrFilter = L"Rich Text Format (*.rtf)\0*.rtf\0Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0"; dialog.lpstrFile = path.data(); dialog.nMaxFile = static_cast(path.size()); dialog.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; if (GetOpenFileNameW(&dialog)) { open_path(path.data()); } } void open_drop(const HDROP drop) { std::array path{}; if (DragQueryFileW(drop, 0, path.data(), static_cast(path.size())) != 0 && prompt_save_changes()) { open_path(path.data()); } DragFinish(drop); } void open_path(const std::wstring& path) { if (lower_extension(path) == L".doc") { MessageBoxW(main_window_, L"The native x64 document engine does not read binary Word 1.x .doc files yet. " L"Open an RTF or text document for this migration milestone.", kProductName, MB_OK | MB_ICONINFORMATION); return; } try { loading_ = true; if (lower_extension(path) == L".rtf") { const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError())); } EDITSTREAM stream{reinterpret_cast(file), 0, &stream_in}; SetWindowTextW(editor_, L""); SendMessageW(editor_, EM_STREAMIN, SF_RTF, reinterpret_cast(&stream)); CloseHandle(file); if (stream.dwError != 0) { throw std::runtime_error("Rich Edit could not read the RTF stream"); } } else { const auto text = decode_text(read_file(path)); SetWindowTextW(editor_, text.c_str()); } current_path_ = path; loading_ = false; set_clean(); } catch (const std::exception& error) { loading_ = false; const std::wstring detail(error.what(), error.what() + std::strlen(error.what())); MessageBoxW(main_window_, (L"Could not open the document.\n\n" + detail).c_str(), kProductName, MB_OK | MB_ICONERROR); } } bool save(const bool save_as) { std::wstring path = current_path_; if (save_as || path.empty()) { std::array selected{}; if (!path.empty()) { wcsncpy_s(selected.data(), selected.size(), path.c_str(), _TRUNCATE); } else { wcscpy_s(selected.data(), selected.size(), L"Document.rtf"); } OPENFILENAMEW dialog{sizeof(dialog)}; dialog.hwndOwner = main_window_; dialog.lpstrFilter = L"Rich Text Format (*.rtf)\0*.rtf\0UTF-8 text (*.txt)\0*.txt\0"; dialog.lpstrFile = selected.data(); dialog.nMaxFile = static_cast(selected.size()); dialog.lpstrDefExt = L"rtf"; dialog.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (!GetSaveFileNameW(&dialog)) { return false; } path = selected.data(); } try { if (lower_extension(path) == L".rtf") { const HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError())); } EDITSTREAM stream{reinterpret_cast(file), 0, &stream_out}; SendMessageW(editor_, EM_STREAMOUT, SF_RTF, reinterpret_cast(&stream)); CloseHandle(file); if (stream.dwError != 0) { throw std::runtime_error("Rich Edit could not write the RTF stream"); } } else { write_file(path, encode_utf8(document_text())); } current_path_ = path; set_clean(); return true; } catch (const std::exception& error) { const std::wstring detail(error.what(), error.what() + std::strlen(error.what())); MessageBoxW(main_window_, (L"Could not save the document.\n\n" + detail).c_str(), kProductName, MB_OK | MB_ICONERROR); return false; } } bool prompt_save_changes() { if (SendMessageW(editor_, EM_GETMODIFY, 0, 0) == 0) { return true; } const int answer = MessageBoxW(main_window_, L"Save changes to this document?", kProductName, MB_YESNOCANCEL | MB_ICONQUESTION); return answer == IDNO || (answer == IDYES && save(false)); } void set_clean() { SendMessageW(editor_, EM_SETMODIFY, FALSE, 0); update_title(); status_dirty_ = true; } void update_title() const { std::wstring name = current_path_.empty() ? L"Document" : std::filesystem::path(current_path_).filename().wstring(); if (!loading_ && SendMessageW(editor_, EM_GETMODIFY, 0, 0) != 0) { name += L" *"; } SetWindowTextW(main_window_, (name + L" - " + kProductName).c_str()); } [[nodiscard]] std::wstring document_text() const { const int length = GetWindowTextLengthW(editor_); std::wstring text(static_cast(length) + 1, L'\0'); if (length != 0) { GetWindowTextW(editor_, text.data(), length + 1); } text.resize(static_cast(length)); return text; } [[nodiscard]] static std::pair count_text( const std::wstring_view text) { std::size_t words = 0; std::size_t characters = 0; bool in_word = false; for (const wchar_t character : text) { if (character != L'\r' && character != L'\n') { ++characters; } const bool word_character = std::iswalnum(character) != 0 || character == L'_'; if (word_character && !in_word) { ++words; } in_word = word_character; } return {words, characters}; } void update_status() { const auto text = document_text(); const auto [words, characters] = count_text(text); CHARRANGE selection{}; SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast(&selection)); const LRESULT line = SendMessageW(editor_, EM_LINEFROMCHAR, selection.cpMin, 0); const LRESULT line_start = SendMessageW(editor_, EM_LINEINDEX, line, 0); const std::wstring file = current_path_.empty() ? L" New document" : L" " + current_path_; const std::wstring counts = L"Words: " + std::to_wstring(words) + L" Characters: " + std::to_wstring(characters); const std::wstring position = L"Ln " + std::to_wstring(line + 1) + L", Col " + std::to_wstring(selection.cpMin - line_start + 1); SendMessageW(status_, SB_SETTEXTW, 0, reinterpret_cast(file.c_str())); SendMessageW(status_, SB_SETTEXTW, 1, reinterpret_cast(counts.c_str())); SendMessageW(status_, SB_SETTEXTW, 2, reinterpret_cast(position.c_str())); status_dirty_ = false; } void show_word_count() const { const auto [words, characters] = count_text(document_text()); const std::wstring message = L"Words: " + std::to_wstring(words) + L"\nCharacters (excluding paragraph marks): " + std::to_wstring(characters); MessageBoxW(main_window_, message.c_str(), L"Word Count", MB_OK | MB_ICONINFORMATION); } void toggle_effect(const DWORD mask, const DWORD effect) const { CHARFORMAT2W format{sizeof(format)}; format.dwMask = mask; SendMessageW(editor_, EM_GETCHARFORMAT, SCF_SELECTION, reinterpret_cast(&format)); format.dwEffects ^= effect; SendMessageW(editor_, EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast(&format)); } void set_alignment(const WORD alignment) const { PARAFORMAT2 format{sizeof(format)}; format.dwMask = PFM_ALIGNMENT; format.wAlignment = alignment; SendMessageW(editor_, EM_SETPARAFORMAT, 0, reinterpret_cast(&format)); } void choose_font() const { CHARFORMAT2W current{sizeof(current)}; SendMessageW(editor_, EM_GETCHARFORMAT, SCF_SELECTION, reinterpret_cast(¤t)); HDC dc = GetDC(editor_); LOGFONTW font{}; font.lfHeight = -MulDiv(current.yHeight / 20, GetDeviceCaps(dc, LOGPIXELSY), 72); font.lfWeight = (current.dwEffects & CFE_BOLD) != 0 ? FW_BOLD : FW_NORMAL; font.lfItalic = (current.dwEffects & CFE_ITALIC) != 0; font.lfUnderline = (current.dwEffects & CFE_UNDERLINE) != 0; wcsncpy_s(font.lfFaceName, current.szFaceName, _TRUNCATE); ReleaseDC(editor_, dc); CHOOSEFONTW dialog{sizeof(dialog)}; dialog.hwndOwner = main_window_; dialog.lpLogFont = &font; dialog.iPointSize = current.yHeight / 2; dialog.rgbColors = current.crTextColor; dialog.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_EFFECTS; if (!ChooseFontW(&dialog)) { return; } CHARFORMAT2W selected{sizeof(selected)}; selected.dwMask = CFM_FACE | CFM_SIZE | CFM_COLOR | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE; selected.yHeight = dialog.iPointSize * 2; selected.crTextColor = dialog.rgbColors; selected.dwEffects = (font.lfWeight >= FW_BOLD ? CFE_BOLD : 0) | (font.lfItalic ? CFE_ITALIC : 0) | (font.lfUnderline ? CFE_UNDERLINE : 0); wcsncpy_s(selected.szFaceName, font.lfFaceName, _TRUNCATE); SendMessageW(editor_, EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast(&selected)); } void show_find() { if (find_dialog_ != nullptr) { SetForegroundWindow(find_dialog_); return; } find_state_ = {}; find_state_.lStructSize = sizeof(find_state_); find_state_.hwndOwner = main_window_; find_state_.Flags = FR_DOWN; find_state_.lpstrFindWhat = find_text_.data(); find_state_.wFindWhatLen = static_cast(find_text_.size()); find_dialog_ = FindTextW(&find_state_); } void on_find_message(const FINDREPLACEW& message) { if ((message.Flags & FR_DIALOGTERM) != 0) { find_dialog_ = nullptr; return; } if ((message.Flags & FR_FINDNEXT) != 0) { find_flags_ = message.Flags; find_next(message.Flags); } } void find_next(const DWORD flags) { if (find_text_[0] == L'\0') { show_find(); return; } CHARRANGE selection{}; SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast(&selection)); const LONG text_length = GetWindowTextLengthW(editor_); FINDTEXTEXW search{}; search.lpstrText = find_text_.data(); const bool down = (flags & FR_DOWN) != 0; search.chrg = down ? CHARRANGE{selection.cpMax, text_length} : CHARRANGE{selection.cpMin - 1, 0}; DWORD options = down ? FR_DOWN : 0; options |= flags & (FR_MATCHCASE | FR_WHOLEWORD); LRESULT result = SendMessageW(editor_, EM_FINDTEXTEXW, options, reinterpret_cast(&search)); if (result == -1) { search.chrg = down ? CHARRANGE{0, text_length} : CHARRANGE{text_length, 0}; result = SendMessageW(editor_, EM_FINDTEXTEXW, options, reinterpret_cast(&search)); } if (result == -1) { MessageBeep(MB_ICONINFORMATION); return; } SendMessageW(editor_, EM_EXSETSEL, 0, reinterpret_cast(&search.chrgText)); SendMessageW(editor_, EM_SCROLLCARET, 0, 0); SetFocus(editor_); } void print_document() const { PRINTDLGW dialog{sizeof(dialog)}; dialog.hwndOwner = main_window_; dialog.Flags = PD_RETURNDC | PD_NOSELECTION | PD_USEDEVMODECOPIESANDCOLLATE; if (!PrintDlgW(&dialog)) { return; } DOCINFOW document{sizeof(document)}; document.lpszDocName = current_path_.empty() ? L"Document" : current_path_.c_str(); if (StartDocW(dialog.hDC, &document) <= 0) { DeleteDC(dialog.hDC); return; } FORMATRANGE range{}; range.hdc = dialog.hDC; range.hdcTarget = dialog.hDC; const int dpi_x = GetDeviceCaps(dialog.hDC, LOGPIXELSX); const int dpi_y = GetDeviceCaps(dialog.hDC, LOGPIXELSY); range.rcPage = {0, 0, MulDiv(GetDeviceCaps(dialog.hDC, PHYSICALWIDTH), 1440, dpi_x), MulDiv(GetDeviceCaps(dialog.hDC, PHYSICALHEIGHT), 1440, dpi_y)}; constexpr LONG margin = 1080; range.rc = {range.rcPage.left + margin, range.rcPage.top + margin, range.rcPage.right - margin, range.rcPage.bottom - margin}; range.chrg = {0, -1}; LONG next = 0; const LONG length = GetWindowTextLengthW(editor_); bool failed = false; while (next < length && !failed) { if (StartPage(dialog.hDC) <= 0) { failed = true; break; } range.chrg.cpMin = next; next = static_cast(SendMessageW(editor_, EM_FORMATRANGE, TRUE, reinterpret_cast(&range))); if (EndPage(dialog.hDC) <= 0 || next <= range.chrg.cpMin) { failed = true; } } SendMessageW(editor_, EM_FORMATRANGE, FALSE, 0); failed ? AbortDoc(dialog.hDC) : EndDoc(dialog.hDC); DeleteDC(dialog.hDC); } void show_about() const { const std::wstring message = L"Microsoft Word for Windows — native x64 migration\n\n" L"This executable is AMD64 and contains no 16-bit assembly.\n" L"Legacy assembly modules accounted for: " + std::to_wstring(opus::x64::assembly_manifest_count()) + L"\n\nCurrent document formats: RTF and UTF-8 text."; MessageBoxW(main_window_, message.c_str(), L"About", MB_OK | MB_ICONINFORMATION); } static DWORD CALLBACK stream_in(const DWORD_PTR cookie, const LPBYTE buffer, const LONG byte_count, LONG* bytes_read) { DWORD read = 0; if (!ReadFile(reinterpret_cast(cookie), buffer, static_cast(byte_count), &read, nullptr)) { *bytes_read = 0; return GetLastError(); } *bytes_read = static_cast(read); return 0; } static DWORD CALLBACK stream_out(const DWORD_PTR cookie, const LPBYTE buffer, const LONG byte_count, LONG* bytes_written) { DWORD written = 0; if (!WriteFile(reinterpret_cast(cookie), buffer, static_cast(byte_count), &written, nullptr)) { *bytes_written = 0; return GetLastError(); } *bytes_written = static_cast(written); return 0; } void show_error(const std::wstring_view message, const DWORD error) const { const std::wstring detail = std::wstring(message) + L".\n\n" + system_error_message(error); MessageBoxW(main_window_, detail.c_str(), kProductName, MB_OK | MB_ICONERROR); } HINSTANCE instance_{}; HMODULE rich_edit_library_{}; HWND main_window_{}; HWND editor_{}; HWND status_{}; HWND find_dialog_{}; UINT find_message_{}; FINDREPLACEW find_state_{}; std::array find_text_{}; DWORD find_flags_{FR_DOWN}; std::wstring current_path_; bool loading_{}; bool status_dirty_{true}; }; } // namespace int WINAPI wWinMain(const HINSTANCE instance, HINSTANCE, PWSTR, const int show_command) { WordApplication application(instance); return application.run(show_command); }