commit abff7464e032d6d7b30cb6499734016fbaeaac4d
Author: fanlusky <1730976608@qq.com>
Date: Tue Mar 25 15:27:42 2025 +0800
first commit
diff --git a/server/.clang-format b/server/.clang-format
new file mode 100644
index 00000000..6f12b1ec
--- /dev/null
+++ b/server/.clang-format
@@ -0,0 +1,6 @@
+---
+BasedOnStyle: Microsoft
+IndentWidth: 4
+
+SortIncludes: true
+ColumnLimit: 80
diff --git a/server/.clangd b/server/.clangd
new file mode 100644
index 00000000..f4f6ed6c
--- /dev/null
+++ b/server/.clangd
@@ -0,0 +1,18 @@
+CompileFlags:
+ Add: [
+ -DUNICODE,
+ -D_UNICODE,
+ -DFANY_DEBUG,
+ "-std=c++17",
+ "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\includes", # you should replace this to your own include path of this project
+ "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\fmt\\include", # you should replace this to your own include path of fmt
+ "-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.web.webview2\\1.0.3124.44\\build\\native\\include",
+ "-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.windows.implementationlibrary\\1.0.240803.1\\include",
+ -Wno-microsoft-goto,
+ -Wno-nonportable-include-path,
+ -Wno-microsoft-extra-qualification,
+ # -Wno-unused-value,
+ ] # ignore some specific warnings
+ CompilationDatabase: build/ # Search build/ directory for compile_commands.json
+# Diagnostics:
+# UnusedIncludes: None # ignore unused includes
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 00000000..875136fb
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,2 @@
+build/
+log.*
\ No newline at end of file
diff --git a/server/.gitmodules b/server/.gitmodules
new file mode 100644
index 00000000..a00d0827
--- /dev/null
+++ b/server/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "fmt"]
+ path = fmt
+ url = https://github.com/fmtlib/fmt.git
diff --git a/server/.vscode/custom.code-snippets b/server/.vscode/custom.code-snippets
new file mode 100644
index 00000000..f62f848a
--- /dev/null
+++ b/server/.vscode/custom.code-snippets
@@ -0,0 +1,11 @@
+{
+ "Debug snippets": {
+ "scope": "c,cpp",
+ "prefix": "dbug",
+ "body": [
+ "#ifdef FANY_DEBUG",
+ "#endif",
+ ],
+ "description": "Add debug macro"
+ }
+}
\ No newline at end of file
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
new file mode 100644
index 00000000..74a1542a
--- /dev/null
+++ b/server/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 3.25.0)
+
+set(PROJECT_NAME "fanimeui")
+
+project(${PROJECT_NAME} VERSION 1.0)
+
+add_subdirectory(fmt)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+
+
+add_definitions(
+ /D_UNICODE=1
+ /DUNICODE=1
+)
+
+# add_definitions(
+# /DFANY_DEBUG=1
+# )
+
+include_directories("./includes/")
+
+set(HEADER_FILES
+ ./includes/main.h
+)
+
+set(SRC_FILES
+ "src/webview2/MainWindow.cpp"
+)
+
+add_executable(fanimeui WIN32
+ ${HEADER_FILES}
+ ${SRC_FILES}
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_WebView2LoaderPreference "Static")
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_USER_PROPS "${CMAKE_SOURCE_DIR}/FanIME_UI.props")
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.Web.WebView2_1.0.3124.44;Microsoft.Windows.ImplementationLibrary_1.0.240803.1")
+
+target_link_libraries(${PROJECT_NAME} fmt::fmt)
\ No newline at end of file
diff --git a/server/FanIME_UI.props b/server/FanIME_UI.props
new file mode 100644
index 00000000..bb243e0e
--- /dev/null
+++ b/server/FanIME_UI.props
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ PackageReference
+ true
+
+
+ x64
+ native
+
+
+ $(UserProfile)\.nuget\packages
+
+
+ Static
+
+
+ native,Version=v0.0
+ win;win-x86;win-x64;win-arm;win-arm64
+
+
\ No newline at end of file
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 00000000..7873fa48
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,7 @@
+# Win32 direct2d and some webview experiment
+
+Try to figure out all needed rendering things about Windows TSF input method.
+
+
+
+
diff --git a/server/assets/background.jpg b/server/assets/background.jpg
new file mode 100644
index 00000000..ca32989a
Binary files /dev/null and b/server/assets/background.jpg differ
diff --git a/server/fmt b/server/fmt
new file mode 160000
index 00000000..b2dfcb2b
--- /dev/null
+++ b/server/fmt
@@ -0,0 +1 @@
+Subproject commit b2dfcb2b805f8670d5a1fa4ed0a70b3c92c6a94e
diff --git a/server/html/horizontal_candidate_window_dark.html b/server/html/horizontal_candidate_window_dark.html
new file mode 100644
index 00000000..2f17d976
--- /dev/null
+++ b/server/html/horizontal_candidate_window_dark.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+ 横向候选框
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/html/vertical_candidate_window_dark.html b/server/html/vertical_candidate_window_dark.html
new file mode 100644
index 00000000..47936cc6
--- /dev/null
+++ b/server/html/vertical_candidate_window_dark.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+ 垂直候选框
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/includes/main.h b/server/includes/main.h
new file mode 100644
index 00000000..64fd1b18
--- /dev/null
+++ b/server/includes/main.h
@@ -0,0 +1,3 @@
+#include
+#include
+#include
diff --git a/server/scripts/lcompile.ps1 b/server/scripts/lcompile.ps1
new file mode 100644
index 00000000..7cdd23e9
--- /dev/null
+++ b/server/scripts/lcompile.ps1
@@ -0,0 +1,26 @@
+# generate compile to exe files
+$currentDirectory = Get-Location
+$cmakeListsPath = Join-Path -Path $currentDirectory -ChildPath "CMakeLists.txt"
+
+if (-not (Test-Path $cmakeListsPath))
+{
+ Write-Host("No CMakeLists.txt in current directory, please check.")
+ return
+}
+
+Write-Host "Start generating and compiling..."
+
+$buildFolderPath = ".\build"
+
+if (-not (Test-Path $buildFolderPath))
+{
+ New-Item -ItemType Directory -Path $buildFolderPath | Out-Null
+ Write-Host "build folder created."
+}
+
+cmake -G "Visual Studio 17 2022" -A x64 -S . -B ./build/
+
+if ($LASTEXITCODE -eq 0)
+{
+ cmake --build ./build/ --config DEBUG
+}
diff --git a/server/scripts/llaunch.ps1 b/server/scripts/llaunch.ps1
new file mode 100644
index 00000000..6d6e1c3e
--- /dev/null
+++ b/server/scripts/llaunch.ps1
@@ -0,0 +1,65 @@
+#
+# generate, compile and run exe files
+#
+
+function getExePathFromCMakeLists()
+{
+ $content = Get-Content -Path "./CMakeLists.txt"
+ $lastLine = ""
+ $contentContainedExeName = ""
+ foreach($line in $content)
+ {
+ if ($line.StartsWith("add_executable"))
+ {
+ $index = $line.IndexOf("(")
+ $contentContainedExeName = $line.Substring($index + 1)
+ if ([string]::IsNullOrEmpty($contentContainedExeName))
+ {
+ $lastLine = $line
+ continue
+ }
+ break
+ } elseif ($lastLine.StartsWith("add_executable"))
+ {
+ $contentContainedExeName = $line
+ break
+ }
+ $lastLine = $line
+ }
+ $result = -split $contentContainedExeName
+ $exePath = "./build/DEBUG/" + $result[0] + ".exe"
+ return $exePath
+}
+
+$currentDirectory = Get-Location
+$cmakeListsPath = Join-Path -Path $currentDirectory -ChildPath "CMakeLists.txt"
+
+if (-not (Test-Path $cmakeListsPath))
+{
+ Write-Host("No CMakeLists.txt in current directory, please check.")
+ return
+}
+
+Write-Host "Start generating and compiling..."
+
+$buildFolderPath = ".\build"
+
+if (-not (Test-Path $buildFolderPath))
+{
+ New-Item -ItemType Directory -Path $buildFolderPath | Out-Null
+ Write-Host "build folder created."
+}
+
+cmake -G "Visual Studio 17 2022" -A x64 -S . -B ./build/
+
+if ($LASTEXITCODE -eq 0)
+{
+ cmake --build ./build/ --config DEBUG
+ if ($LASTEXITCODE -eq 0)
+ {
+ $exePath = getExePathFromCMakeLists
+ Write-Host "start running as follows..."
+ Write-Host "=================================================="
+ Invoke-Expression $exePath
+ }
+}
diff --git a/server/scripts/lrun.ps1 b/server/scripts/lrun.ps1
new file mode 100644
index 00000000..4b3d149f
--- /dev/null
+++ b/server/scripts/lrun.ps1
@@ -0,0 +1,38 @@
+#
+# run exe file that has already been compiled before
+#
+
+
+function getExePathFromCMakeLists()
+{
+ $content = Get-Content -Path "./CMakeLists.txt"
+ $lastLine = ""
+ $contentContainedExeName = ""
+ foreach($line in $content)
+ {
+ if ($line.StartsWith("add_executable"))
+ {
+ $index = $line.IndexOf("(")
+ $contentContainedExeName = $line.Substring($index + 1)
+ if ([string]::IsNullOrEmpty($contentContainedExeName))
+ {
+ $lastLine = $line
+ continue
+ }
+ break
+ } elseif ($lastLine.StartsWith("add_executable"))
+ {
+ $contentContainedExeName = $line
+ break
+ }
+ $lastLine = $line
+ }
+ $result = -split $contentContainedExeName
+ $exePath = "./build/DEBUG/" + $result[0] + ".exe"
+ return $exePath
+}
+
+$exePath = getExePathFromCMakeLists
+Write-Host "start running as follows..."
+Write-Host "=================================================="
+Invoke-Expression $exePath
diff --git a/server/src/main.cpp b/server/src/main.cpp
new file mode 100644
index 00000000..224fe12c
--- /dev/null
+++ b/server/src/main.cpp
@@ -0,0 +1,252 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#pragma comment(lib, "d2d1")
+#pragma comment(lib, "dwrite")
+#pragma comment(lib, "dwmapi.lib")
+
+ID2D1Factory *pD2DFactory = nullptr;
+ID2D1HwndRenderTarget *pRenderTarget = nullptr;
+ID2D1SolidColorBrush *pBrush = nullptr;
+IDWriteFactory *pDWriteFactory = nullptr;
+IDWriteTextFormat *pTextFormat = nullptr;
+
+LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+FLOAT GetWindowScale(HWND hwnd);
+
+bool InitD2DAndDWrite()
+{
+
+ HRESULT hr = D2D1CreateFactory( //
+ D2D1_FACTORY_TYPE_SINGLE_THREADED, //
+ &pD2DFactory //
+ );
+ if (FAILED(hr))
+ return false;
+
+ hr = DWriteCreateFactory( //
+ DWRITE_FACTORY_TYPE_SHARED, //
+ __uuidof(IDWriteFactory), //
+ reinterpret_cast(&pDWriteFactory) //
+ );
+ if (FAILED(hr))
+ return false;
+
+ hr = pDWriteFactory->CreateTextFormat(L"Segoe UI", //
+ nullptr, //
+ DWRITE_FONT_WEIGHT_NORMAL, //
+ DWRITE_FONT_STYLE_NORMAL, //
+ DWRITE_FONT_STRETCH_NORMAL, //
+ 24.0f, //
+ L"en-us", //
+ &pTextFormat //
+ );
+ if (FAILED(hr))
+ return false;
+
+ return true;
+}
+
+bool InitD2DRenderTarget(HWND hwnd)
+{
+ if (!pD2DFactory)
+ return false;
+
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+
+ auto renderTargetProps = D2D1::RenderTargetProperties( //
+ D2D1_RENDER_TARGET_TYPE_DEFAULT, //
+ D2D1::PixelFormat( //
+ DXGI_FORMAT_UNKNOWN, //
+ D2D1_ALPHA_MODE_PREMULTIPLIED //
+ ) //
+ );
+ auto hwndRenderTargetProps = D2D1::HwndRenderTargetProperties( //
+ hwnd, //
+ D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top), //
+ D2D1_PRESENT_OPTIONS_IMMEDIATELY //
+ );
+
+ HRESULT hr = pD2DFactory->CreateHwndRenderTarget( //
+ renderTargetProps, //
+ hwndRenderTargetProps, //
+ &pRenderTarget //
+ );
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pRenderTarget->CreateSolidColorBrush( //
+ D2D1::ColorF(D2D1::ColorF::White), //
+ &pBrush //
+ );
+ }
+
+ return SUCCEEDED(hr);
+}
+
+void OnPaint(HWND hwnd)
+{
+ if (!pRenderTarget)
+ return;
+
+ pRenderTarget->BeginDraw();
+
+ // Clear baakground to transparent
+ // pRenderTarget->Clear(D2D1::ColorF(0, 0, 0, 0));
+ pRenderTarget->Clear(D2D1::ColorF(25 / 255.0, 25 / 255.0, 25 / 255.0, 0.5));
+
+ /*
+ FLOAT scale = GetWindowScale(hwnd);
+
+ D2D1_RECT_F borderRect = D2D1::RectF( //
+ static_cast(0), //
+ static_cast(0), //
+ static_cast(600 / scale), //
+ static_cast(600 / scale) //
+ );
+ pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::White, 1.0f));
+E
+ pRenderTarget->FillRectangle(D2D1::RectF(50, 50, 400, 300), pBrush)
+
+ pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red, 1.0f));
+
+ pRenderTarget->FillRectangle(D2D1::RectF(200, 200, 500, 400), pBrush);
+
+ pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Green, 0.3f));
+
+ pRenderTarget->FillRectangle(D2D1::RectF(300, 100, 600, 250), pBrush);
+
+ pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::White, 1.0f)); //
+ pRenderTarget->DrawTextW( //
+ L"Transparent Window and Direct2D", //
+ 31, //
+ pTextFormat, //
+ D2D1::RectF(5, 5, 600, 200), //
+ pBrush //
+ );
+ */
+
+ HRESULT hr = pRenderTarget->EndDraw();
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ pRenderTarget->Release();
+ InitD2DRenderTarget(hwnd);
+ }
+}
+
+HWND CreateTransparentWindow(HINSTANCE hInstance)
+{
+ WNDCLASS wc = {0};
+ wc.lpfnWndProc = WndProc;
+
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.hInstance = hInstance;
+ wc.lpszClassName = L"TransparentWindowClass";
+
+ if (!RegisterClass(&wc))
+ return nullptr;
+
+ HWND hwnd = CreateWindowEx(WS_EX_LAYERED, //
+ wc.lpszClassName, //
+ L"TransparentWindow", //
+ WS_POPUP, //
+ 100, 100, 600, 600, //
+ nullptr, nullptr, hInstance, nullptr);
+
+ if (hwnd)
+ {
+ SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
+
+ MARGINS mar = {-1};
+ DwmExtendFrameIntoClientArea(hwnd, &mar);
+ }
+
+ return hwnd;
+}
+
+LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_CREATE:
+ InitD2DRenderTarget(hwnd);
+ return 0;
+
+ case WM_PAINT:
+ case WM_DISPLAYCHANGE:
+ OnPaint(hwnd);
+ ValidateRect(hwnd, nullptr);
+ return 0;
+ case WM_MOUSEMOVE: {
+ float x = (float)LOWORD(lParam);
+ float y = (float)HIWORD(lParam);
+
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+ if (x >= rc.left && x <= rc.right && y >= rc.top && y <= rc.bottom)
+ {
+ SetCursor(LoadCursor(nullptr, IDC_ARROW));
+ }
+ break;
+ }
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return 0;
+
+ default:
+ return DefWindowProc(hwnd, message, wParam, lParam);
+ }
+
+ return 0;
+}
+
+int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPWSTR lpCmdLine, int nCmdShow)
+{
+ SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+
+ if (!InitD2DAndDWrite())
+ return -1;
+
+ HWND hwnd = CreateTransparentWindow(hInstance);
+
+ if (!hwnd)
+ return -1;
+
+ ShowWindow(hwnd, nCmdShow);
+ UpdateWindow(hwnd);
+
+ MSG msg;
+ while (GetMessage(&msg, nullptr, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ if (pBrush)
+ pBrush->Release();
+ if (pRenderTarget)
+ pRenderTarget->Release();
+ if (pDWriteFactory)
+ pDWriteFactory->Release();
+ if (pTextFormat)
+ pTextFormat->Release();
+ if (pD2DFactory)
+ pD2DFactory->Release();
+
+ return (int)msg.wParam;
+}
+
+FLOAT GetWindowScale(HWND hwnd)
+{
+ UINT dpi = GetDpiForWindow(hwnd);
+ FLOAT scale = dpi / 96.0f;
+ return scale;
+}
\ No newline at end of file
diff --git a/server/src/webview2/MainWindow.cpp b/server/src/webview2/MainWindow.cpp
new file mode 100644
index 00000000..9784b5b7
--- /dev/null
+++ b/server/src/webview2/MainWindow.cpp
@@ -0,0 +1,574 @@
+#include "WebView2.h"
+#include "fmt/core.h"
+#include "fmt/xchar.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//
+// Message define
+//
+UINT WM_SHOW_MAIN_WINDOW;
+UINT WM_HIDE_MAIN_WINDOW;
+UINT WM_MOVE_CANDIDATE_WINDOW;
+
+static TCHAR szWindowClass[] = _T("global_candidate_window");
+static TCHAR szTitle[] = _T("WebView sample");
+
+std::wstring candStr = L"";
+
+static std::wstring HTMLString = LR"(
+
+
+
+
+
+
+ Vertical Candidate Window
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)";
+
+static std::wstring BodyString = LR"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)";
+
+HINSTANCE hInst = 0;
+
+using namespace Microsoft::WRL;
+
+static ComPtr webviewController;
+static ComPtr webview;
+ComPtr webview3;
+ComPtr webviewController2;
+
+void LogMessageW(const wchar_t *message)
+{
+ std::time_t now = std::time(nullptr);
+ std::tm *localTime = std::localtime(&now);
+
+ wchar_t timeBuffer[80];
+ wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t),
+ L"%Y-%m-%d %H:%M:%S", localTime);
+
+ std::wstring logfilePath = L"C:"
+ L"\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Cod"
+ L"es\\DirectWriteExperiment\\log.txt";
+ std::wofstream logFile(logfilePath, std::ios_base::app);
+ if (logFile.is_open())
+ {
+ logFile.imbue(std::locale("Chinese_China.65001"));
+ logFile << L"[" << timeBuffer << L"] " << message;
+ logFile << std::endl;
+ logFile.close();
+ }
+}
+
+void UpdateHtmlContentWithJavaScript(ComPtr webview,
+ const std::wstring &newContent)
+{
+ if (webview != nullptr)
+ {
+ std::wstring script =
+ L"document.body.innerHTML = `" + newContent + L"`;";
+ webview->ExecuteScript(script.c_str(), nullptr);
+ }
+}
+
+void MeasureDomUpdateTime(ComPtr webview)
+{
+ std::wstring script =
+ LR"(document.body.innerHTML = '1. 原来
2. 如此
3. 竟然
4. 这样
5. 可恶
6. 棋盘
7. 磨合
8. 樱花