スポンサーリンク

Win32APIでCEFのオフスクリーンレンダリング 日本語入力

#include <windows.h>

#include <optional>
#include <iostream>
#include <include/cef_app.h>


#ifdef _DEBUG
#pragma comment(lib, R"(D:\libraries\CEF\cef_binary_138.0.15+gd0f1f64+chromium-138.0.7204.50_windows64\Debug\libcef.lib)")
#pragma comment(lib, R"(D:\libraries\CEF\MD\Debug\libcef_dll_wrapper.lib)")
#else
#pragma comment(lib, R"(D:\libraries\CEF\cef_binary_138.0.15+gd0f1f64+chromium-138.0.7204.50_windows64\Release\libcef.lib)")
#pragma comment(lib, R"(D:\libraries\CEF\MD\Release\libcef_dll_wrapper.lib)")
#endif

#include "MyCEFControl.hpp"

#include <Windows.h>
#include <windowsx.h>
#include <imm.h>

#pragma comment(lib, "imm32.lib")

void OnPaint(HWND hwnd, HDC hdc) {

    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (handler) {
        CefRefPtr<CefRenderHandler> baseRenderHandler = handler->GetRenderHandler();
        MyRenderHandler* renderHandler = static_cast<MyRenderHandler*>(baseRenderHandler.get());
        RGBAImage img = renderHandler->m_browserImage;
        if (img.buffer.size() > 0) {
            // ビットマップ情報の設定
            BITMAPINFO bmi;
            ZeroMemory(&bmi, sizeof(BITMAPINFO));
            bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            bmi.bmiHeader.biWidth = img.width;
            bmi.bmiHeader.biHeight = -img.height; // 上下反転
            bmi.bmiHeader.biPlanes = 1;
            bmi.bmiHeader.biBitCount = 24; // RGB24ビット
            bmi.bmiHeader.biCompression = BI_RGB;
            // 画像データを描画
            SetDIBitsToDevice(
                hdc,
                0, 0,                  // 描画位置
                img.width, img.height, // 描画サイズ
                0, 0,                  // ソースの開始位置
                0, img.height,         // ソースの開始ラインとライン数
                img.buffer.data(),     // ピクセルデータ
                &bmi,
                DIB_RGB_COLORS         // カラーテーブルの使用方法
            );
        }
    }
}



void OnMouseMove(HWND hwnd, int x, int y) {
    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (handler) {
        CefMouseEvent mouse_event;
        mouse_event.x = x;
        mouse_event.y = y;
        mouse_event.modifiers = 0;
        handler->GetBrowser()->GetHost()->SendMouseMoveEvent(mouse_event, false);
        InvalidateRect(hwnd, NULL, FALSE);
    }
}



void OnLButtonDown(HWND hwnd, int x, int y) {
    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (handler) {
        CefMouseEvent mouse_event;
        mouse_event.x = x;
        mouse_event.y = y;
        mouse_event.modifiers = 0;
        handler->GetBrowser()->GetHost()->SendMouseClickEvent(mouse_event, cef_mouse_button_type_t::MBT_LEFT, false, 1);
        InvalidateRect(hwnd, NULL, FALSE);
    }
}



void OnLButtonUp(HWND hwnd, int x, int y) {
    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (handler) {
        CefMouseEvent mouse_event;
        mouse_event.x = x;
        mouse_event.y = y;
        mouse_event.modifiers = 0;
        handler->GetBrowser()->GetHost()->SendMouseClickEvent(mouse_event, cef_mouse_button_type_t::MBT_LEFT, true, 1);
        InvalidateRect(hwnd, NULL, FALSE);
    }
}



void OnClose(HWND hwnd) {
    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (handler) {
        handler->CloseAllBrowsers(true);
    }
}

std::optional<LRESULT>  WndProc_IME_Work(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void SendKeyEventToBrowser(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);



LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    MyHandler* handler = nullptr;
    LPCREATESTRUCT pcs;
    PAINTSTRUCT ps;
    HDC hdc;

    CefRefPtr<CefBrowser> browser;
    handler =
        reinterpret_cast<MyHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
    if(handler)
        browser = handler->GetBrowser();


    SendKeyEventToBrowser(hwnd, msg, wp, lp);// キーボード入力のメッセージ処理

    auto imeret = WndProc_IME_Work(hwnd, msg, wp, lp);// IME関係のメッセージ処理
    if (imeret.has_value()) {
        return imeret.value();
    }

    switch (msg) {
    case WM_CLOSE:

        OnClose(hwnd);
        DestroyWindow(hwnd);

        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        OnPaint(hwnd, hdc);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_CREATE:
        pcs = reinterpret_cast<LPCREATESTRUCT>(lp);
        SetWindowLongPtr(hwnd, GWLP_USERDATA,
            reinterpret_cast<LONG_PTR>(pcs->lpCreateParams));

        return 0;
    case WM_DESTROY:
        //CefQuitMessageLoop();
        PostQuitMessage(0);
        return 0;

    case WM_MOUSEMOVE:
        OnMouseMove(hwnd, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
        return 0;
    case WM_LBUTTONDOWN:
        if (handler && browser)
            browser->GetHost()->SetFocus(true);
        OnLButtonDown(hwnd, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
        return 0;
    case WM_LBUTTONUP:
        OnLButtonUp(hwnd, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
        return 0;
    }

    return DefWindowProc(hwnd, msg, wp, lp);
}


///////////////////////////////////////////////
///////////////////////////////////////////////
///////////////////////////////////////////////
void HandleImeComposition(HWND hWnd,
    CefRefPtr<CefBrowser> browser,
    UINT msg, WPARAM wParam, LPARAM lParam);

std::optional<LRESULT> WndProc_IME_Work(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    MyHandler* handler = (MyHandler*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
	CefRefPtr<CefBrowser> browser = nullptr;
    if (handler) {
        browser = handler->GetBrowser();
    }

    switch (msg) {
    case WM_SETFOCUS:
        if (browser) {
            browser->GetHost()->SetFocus(true);
        }
        break;

    case WM_KILLFOCUS:
        if (browser) {
            browser->GetHost()->SetFocus(false);
        }
        break;

    case WM_IME_STARTCOMPOSITION:
        return 0;
    case WM_IME_COMPOSITION:
    {
        HIMC hIMC = ImmGetContext(hWnd);
        if (hIMC) {

            POINT caret{};
            CefRefPtr<CefRenderHandler> baseRenderHandler = handler->GetRenderHandler();
            MyRenderHandler* renderHandler = static_cast<MyRenderHandler*>(baseRenderHandler.get());

            // MyRenderHandler からキャレット位置を取得
            if (renderHandler && renderHandler->GetImeCaretPos(caret)) {


                CANDIDATEFORM cf{};
                cf.dwIndex = 0;
                cf.dwStyle = CFS_CANDIDATEPOS;
                cf.ptCurrentPos = caret;

                ImmSetCandidateWindow(hIMC, &cf);
            }
        }
    }
        if (browser)
            HandleImeComposition(hWnd, browser, msg, wParam, lParam);
        return 0;

    case WM_IME_ENDCOMPOSITION:
        if (browser) {
            browser->GetHost()->ImeFinishComposingText(true);
        }
        if (handler)
        {
            CefRefPtr<CefRenderHandler> baseRenderHandler = handler->GetRenderHandler();
            MyRenderHandler* renderHandler = static_cast<MyRenderHandler*>(baseRenderHandler.get());
            renderHandler->SetImeCaretInvalid();
        }
        return 0;
    }

    return std::nullopt;
}

void HandleImeComposition(HWND hWnd,
    CefRefPtr<CefBrowser> browser,
    UINT msg, WPARAM wParam, LPARAM lParam)
{
    HIMC hIMC = ImmGetContext(hWnd);
    if (!hIMC) return;

    // 変換中文字列の取得 (GCS_COMPSTR)
    if (lParam & GCS_COMPSTR) {
        LONG size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, nullptr, 0);
        std::wstring comp;
        comp.resize(size / sizeof(wchar_t));
        ImmGetCompositionStringW(hIMC, GCS_COMPSTR,
            &comp[0], size);

        // キャレット位置 (GCS_CURSORPOS)
        LONG cursor = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, nullptr, 0);

        // 属性(下線の種類などに使える)
        // LONG attrSize = ImmGetCompositionStringW(hIMC, GCS_COMPATTR, nullptr, 0);
        std::vector<CefCompositionUnderline> underlines;
        // 簡略のため全部同じ属性にしているが、
        // 実際は GCS_COMPATTR の値から変換中部分/確定部分などを判定して設定する
        CefCompositionUnderline ul;
        ul.range = CefRange(0, static_cast<int>(comp.size()));
        ul.color = 0;              // デフォルト色
        ul.background_color = 0;   // デフォルト背景
        ul.thick = false;
        underlines.push_back(ul);

        // selection_range, replacement_range はここでは簡略化
        CefRange selection(cursor, cursor);

        browser->GetHost()->ImeSetComposition(
            comp, underlines,
            CefRange::InvalidRange(), // replacement_range
            selection                  // selection_range
        );
    }

    // 確定文字列の取得 (GCS_RESULTSTR)
    if (lParam & GCS_RESULTSTR) {
        LONG size = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, nullptr, 0);
        std::wstring result;

        
        result.resize(size / sizeof(wchar_t));
        ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, &result[0], size);


        // resultをDebugString
        OutputDebugStringW(result.c_str());

        browser->GetHost()->ImeCommitText(
            result,
            CefRange::InvalidRange(),
            0 // relative_cursor_pos
        );
        browser->GetHost()->ImeFinishComposingText(false);
    }

    ImmReleaseContext(hWnd, hIMC);
}

/////////////////////////////////////////////// /////////////////////////////////////////////// ///////////////////////////////////////////////
void SendKeyEventToBrowser(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    MyHandler* handler =
        reinterpret_cast<MyHandler*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
    if (!handler) return;

    CefRefPtr<CefBrowser> browser = handler->GetBrowser();
    if (!browser) return;

    CefKeyEvent ev;

    switch (msg) {
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        ev.type = KEYEVENT_RAWKEYDOWN;
        break;
    case WM_KEYUP:
    case WM_SYSKEYUP:
        ev.type = KEYEVENT_KEYUP;
        break;
    case WM_CHAR:
    case WM_SYSCHAR:
        ev.type = KEYEVENT_CHAR;
        break;
    default:
        return; // 対象外
    }

    ev.windows_key_code = static_cast<int>(wParam);
    ev.native_key_code = static_cast<int>(lParam);
    ev.is_system_key =
        (msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP || msg == WM_SYSCHAR);

    // 必要なら修飾キーも付与
    ev.modifiers = 0;
    if (GetKeyState(VK_SHIFT) & 0x8000)   ev.modifiers |= EVENTFLAG_SHIFT_DOWN;
    if (GetKeyState(VK_CONTROL) & 0x8000) ev.modifiers |= EVENTFLAG_CONTROL_DOWN;
    if (GetKeyState(VK_MENU) & 0x8000)    ev.modifiers |= EVENTFLAG_ALT_DOWN;

    browser->GetHost()->SendKeyEvent(ev);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow) {


    HWND hwnd;
    MSG msg;
    WNDCLASS winc;

    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = WndProc;
    winc.cbClsExtra = winc.cbWndExtra = 0;
    winc.hInstance = hInstance;
    winc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    winc.hCursor = LoadCursor(NULL, IDC_ARROW);
    winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    winc.lpszMenuName = NULL;
    winc.lpszClassName = TEXT("SZL-WND");

    if (!RegisterClass(&winc)) return -1;

    /////////////////////////////////////////////////////////////////////////
    CefRefPtr<MyHandler> g_handler = MyInitCEF(hInstance);
    if (!g_handler) {
        return -1;
    }
    /////////////////////////////////////////////////////////////////////////
    hwnd = CreateWindow(
        TEXT("SZL-WND"), TEXT("CEF test"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        500, 500,
        NULL, NULL, hInstance, g_handler.get()
    );

    if (hwnd == NULL) return -1;
    /////////////////////////////////////////////////////////////////////////
    //SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)(g_handler.get()));
    /////////////////////////////////////////////////////////////////////////
    MyRenderSetting(hwnd, g_handler);
    /////////////////////////////////////////////////////////////////////////

    // メッセージループ
    CefRunMessageLoop();

    // CEFのシャットダウン
    CefShutdown();


    return 0;
}

MyCEFControl.hpp

#pragma once


#include <include/cef_app.h>

#include <optional>

struct RGBAImage {
    int width;
    int height;
    std::vector<unsigned char> buffer; // RGBAの生データ
};

RGBAImage FromRGBA(const unsigned char* rgba_buffer, int width, int height);

class MyRenderHandler : public CefRenderHandler {
public:
    HWND m_pCanvasWindow = nullptr;
    RGBAImage m_browserImage;
    HWND m_main_hwnd = nullptr; // IME用にメインウィンドウのハンドルを保持
    // 追加: 最後に通知されたキャレット矩形(ブラウザの View 座標系)
    std::optional<CefRect> m_ime_caret_rect;

    // GetViewRect() をoverrideする
    // 必須:ビューポートのサイズを返す
    void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override;

    // 必須:レンダリング結果がここに来る
    void OnPaint(CefRefPtr<CefBrowser> browser,
        PaintElementType type,
        const RectList& dirtyRects,
        const void* buffer,
        int width, int height) override;

    // サイズを外から設定
    void SetSize(int w, int h);

    // メインウィンドウのハンドルを設定
    void SetMainHwnd(HWND hwnd);

    // IMEのキャレット位置が変化したときに呼ばれる
    void OnImeCompositionRangeChanged(
        CefRefPtr<CefBrowser> browser,
        const CefRange& selected_range,
        const CefRenderHandler::RectList& character_bounds) override;

    bool GetImeCaretPos(POINT& pt) const;

    void SetImeCaretInvalid();


private:

    IMPLEMENT_REFCOUNTING(MyRenderHandler);
};

class MyHandler :
    public CefClient,
    public CefLifeSpanHandler,
    public CefRenderHandler {

    CefRefPtr<CefRenderHandler> renderHandler_;

public:

    MyHandler(CefRefPtr<CefRenderHandler> renderHandler)
        : renderHandler_(renderHandler) {
    }

    CefRefPtr<CefRenderHandler> GetRenderHandler() override;


    CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override;

    void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;

    void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;

    void CloseAllBrowsers(bool force_close);

    CefRefPtr<CefBrowser> GetBrowser();

    void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override {
        return renderHandler_->GetViewRect(browser, rect);
    }

    void OnPaint(CefRefPtr<CefBrowser> browser,
        PaintElementType type,
        const RectList& dirtyRects,
        const void* buffer,
        int width, int height) override {
        return renderHandler_->OnPaint(browser, type, dirtyRects, buffer, width, height);
    }

    IMPLEMENT_REFCOUNTING(MyHandler);

private:
    CefRefPtr<CefBrowser> m_Browser;
};

CefRefPtr<MyHandler> MyInitCEF(HINSTANCE hInstance);

void MyRenderSetting(HWND window, CefRefPtr<MyHandler> ghandler);

MyCEFControl.cpp

#include "MyCEFControl.hpp"

void MyRenderHandler::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) {
//    // ビューポートのサイズを返す
    rect = CefRect(0, 0, m_browserImage.width, m_browserImage.height);
}

// 必須:レンダリング結果がここに来る
void MyRenderHandler::OnPaint(CefRefPtr<CefBrowser> browser,
    PaintElementType type,
    const RectList& dirtyRects,
    const void* buffer,
    int width, int height) {

    // ※ buffer は 32bit BGRA フォーマット
    m_browserImage = FromRGBA(static_cast<const unsigned char*>(buffer), width, height);

    InvalidateRect(m_pCanvasWindow, NULL, FALSE);

}

// サイズを外から設定
void MyRenderHandler::SetSize(int w, int h) {
    m_browserImage.buffer.resize(w * h * 4);
    m_browserImage.width = w;
    m_browserImage.height = h;
}


void MyRenderHandler::OnImeCompositionRangeChanged(
    CefRefPtr<CefBrowser> browser,
    const CefRange& selected_range,
    const CefRenderHandler::RectList& character_bounds)
{

    // character_bounds[0] がキャレット位置に対応する矩形
    if (character_bounds.empty())
        return;

    ///////////////////////////////////////////////////////////
#if 0
    int idx = 0;
    // 選択範囲の末尾をキャレットとみなす
    if (selected_range.to > 0 &&
        selected_range.to <= static_cast<int>(character_bounds.size())) {
        idx = selected_range.to - 1;
    }
    else {
        idx = static_cast<int>(character_bounds.size()) - 1;
    }
    m_ime_caret_rect = character_bounds[idx];
#else
        // 常に最初の矩形をキャレット位置とみなす
    m_ime_caret_rect = character_bounds[0];
#endif

}

bool MyRenderHandler::GetImeCaretPos(POINT& pt) const {
    if (!m_ime_caret_rect)
        return false;
    pt.x = m_ime_caret_rect->x;
    pt.y = m_ime_caret_rect->y;
    return true;
}

void MyRenderHandler::SetImeCaretInvalid() {
    m_ime_caret_rect = std::nullopt;
}

void MyRenderHandler::SetMainHwnd(HWND hwnd) {
    m_main_hwnd = hwnd;
}




//=============================================================

CefRefPtr<CefRenderHandler> MyHandler::GetRenderHandler() {
    return renderHandler_;
}

CefRefPtr<CefLifeSpanHandler> MyHandler::GetLifeSpanHandler() {
    return this;
}

void MyHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
    m_Browser = browser;
}

void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
    m_Browser = nullptr;
    CefQuitMessageLoop();

}

void MyHandler::CloseAllBrowsers(bool force_close) {
    if (m_Browser) {
        m_Browser->GetHost()->CloseBrowser(force_close);
    }
}
CefRefPtr<CefBrowser> MyHandler::GetBrowser() {
    return m_Browser;
}

//=============================================================
RGBAImage FromRGBA(const unsigned char* rgba_buffer, int width, int height)
{
    // wxImage用にRGBを抜き出す
    // 既存の生配列からstd::vectorへの変換例
    std::vector<unsigned char> rgb_data(rgba_buffer, rgba_buffer + width * height * 3);

    for (int i = 0; i < width * height; ++i) {
        rgb_data[i * 3 + 0] = rgba_buffer[i * 4 + 0];
        rgb_data[i * 3 + 1] = rgba_buffer[i * 4 + 1];
        rgb_data[i * 3 + 2] = rgba_buffer[i * 4 + 2];
    }

    RGBAImage img;
    img.width = width;
    img.height = height;
    img.buffer = std::move(rgb_data);

    return img;

}

CefRefPtr<MyHandler> MyInitCEF(HINSTANCE hInstance) {

    CefMainArgs main_args(hInstance);
    int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
    if (exit_code >= 0)
        return nullptr;

    // CEFの設定
    CefSettings settings;
    settings.no_sandbox = true;

    // マルチスレッドメッセージループを無効にする
    // オフスクリーンレンダリングでは false必須
    settings.multi_threaded_message_loop = false;


    CefInitialize(main_args, settings, nullptr, nullptr);

    /////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////
    CefRefPtr<MyRenderHandler> renderHandler = new MyRenderHandler();
    CefRefPtr<MyHandler> g_handler = CefRefPtr<MyHandler>(new MyHandler(renderHandler));

    return g_handler;
}

void MyRenderSetting(HWND window, CefRefPtr<MyHandler> ghandler) {

    // MyRenderHandlerを取得
    CefRefPtr<CefRenderHandler> baseRenderHandler = ghandler->GetRenderHandler();
    CefRefPtr<MyRenderHandler> renderHandler = static_cast<MyRenderHandler*>(baseRenderHandler.get());

    renderHandler->m_pCanvasWindow = window;
    renderHandler->SetSize(400, 400); // ビューポートのサイズを設定

    renderHandler->SetMainHwnd(window); // IME用にメインウィンドウハンドルを設定

    CefBrowserSettings browser_settings;

    CefWindowInfo window_info;
    CefRect cefRect(
        0,
        0,
        800,
        600);


    // オフスクリーンレンダリング
    window_info.SetAsWindowless(nullptr);


    CefBrowserHost::CreateBrowser(
        window_info,
        ghandler,
        "https://www.google.com",
        browser_settings,
        nullptr,
        nullptr);



}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: