Repository: rime/weasel (language: cpp)

Issue:
高分屏上候选窗口比例变形
**上报前请检查**
- [x] 我遇到的问题没有其他人在 issue 里提到过
- [x] 我的小狼毫版本于 rime/weasel 下载
- [x] 我在使用小狼毫最新发布版本后的 CI 构建

**操作系统信息**
 - OS 详细版本: [ Windows 11 23H2]
 - 小狼毫版本: [ 492c84fe8d4523e6cd8100c83ba688c885361a12 ]

**描述遇到的问题**
4K 300% 缩放（为了效果更明显）：

Weasel （似乎圆角、空隙都没有被缩放）

![image](https://github.com/rime/weasel/assets/76689045/28a5c413-ddcf-4a3f-9bce-bd57fc77d5f1)
![4](https://github.com/rime/weasel/assets/76689045/79a41d87-a913-4227-93dd-aeae757dc298)

Weasel 1920x1080 125%（期待效果）

![image](https://github.com/rime/weasel/assets/76689045/138ac736-231a-47fb-a948-28d739d2d25d)
![image](https://github.com/rime/weasel/assets/76689045/052cdbb3-a6f7-44e4-8319-18e3e4b896ac)

微软拼音：（高分屏和低分屏保持一致）

![image](https://github.com/rime/weasel/assets/76689045/9d95fc68-6b78-4a5f-bf58-e9dda4e0b09e)





Discussion / hints:
另外两个没太能稳定复现（高频复现，但偶尔正常）的问题，不知道有没有外接屏幕和键盘的人遇到：

- 在屏幕间切换，Weasel 更高概率崩溃
- 外接键盘时，weasel 窗口不断闪烁，任务栏失去响应（看不到状态变化）
![2024年05月14日23时36分](https://github.com/rime/weasel/assets/76689045/96b4a192-c3aa-4149-811a-183c21a6e08a)
是的，layout里的尺寸参数目前未做dpi响应处理，单位仍然是像素不是DIP,可能下次发版处理。

屏幕之间切换未复现（日常双屏工作），外接键盘问题未复现问题（日常台式机外接键盘）
不懂，那可能是我键盘或者显示器的问题？

Fix patch:
diff --git a/WeaselUI/DirectWriteResources.cpp b/WeaselUI/DirectWriteResources.cpp
index 1d51b6e77..9dde3d98a 100644
--- a/WeaselUI/DirectWriteResources.cpp
+++ b/WeaselUI/DirectWriteResources.cpp
@@ -32,8 +32,8 @@ std::vector<std::wstring> wc_split(const wchar_t* in, const wchar_t* delim) {
 DirectWriteResources::DirectWriteResources(weasel::UIStyle& style,
                                            UINT dpi = 96)
     : _style(style),
-      dpiScaleX_(0),
-      dpiScaleY_(0),
+      dpiScaleFontPoint(0),
+      dpiScaleLayout(0),
       pD2d1Factory(NULL),
       pDWFactory(NULL),
       pRenderTarget(NULL),
@@ -70,9 +70,9 @@ DirectWriteResources::DirectWriteResources(weasel::UIStyle& style,
   pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
                                        pBrush.GetAddressOf());
   // get the dpi information
-  dpiScaleX_ = dpiScaleY_ = (float)dpi;
-  dpiScaleX_ /= 72.0f;
-  dpiScaleY_ /= 72.0f;
+  dpiScaleFontPoint = dpiScaleLayout = (float)dpi;
+  dpiScaleFontPoint /= 72.0f;
+  dpiScaleLayout /= 96.0f;
 
   InitResources(style, dpi);
 }
@@ -130,8 +130,8 @@ HRESULT DirectWriteResources::InitResources(
   DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL;
   DWRITE_FONT_STYLE fontStyle = DWRITE_FONT_STYLE_NORMAL;
   // convert percentage to float
-  float linespacing = dpiScaleX_ * ((float)_style.linespacing / 100.0f);
-  float baseline = dpiScaleX_ * ((float)_style.baseline / 100.0f);
+  float linespacing = dpiScaleFontPoint * ((float)_style.linespacing / 100.0f);
+  float baseline = dpiScaleFontPoint * ((float)_style.baseline / 100.0f);
   if (_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT)
     baseline = linespacing / 2;
   // setup font weight and font style by the first unit of font_face setting
@@ -142,7 +142,7 @@ HRESULT DirectWriteResources::InitResources(
                          std::wregex(STYLEORWEIGHT, std::wregex::icase), L"");
   hResult = pDWFactory->CreateTextFormat(
       _mainFontFace.c_str(), NULL, fontWeight, fontStyle,
-      DWRITE_FONT_STRETCH_NORMAL, font_point * dpiScaleX_, L"",
+      DWRITE_FONT_STRETCH_NORMAL, font_point * dpiScaleFontPoint, L"",
       reinterpret_cast<IDWriteTextFormat**>(pTextFormat.GetAddressOf()));
   if (pTextFormat != NULL) {
     if (vertical_text) {
@@ -169,7 +169,7 @@ HRESULT DirectWriteResources::InitResources(
                          std::wregex(STYLEORWEIGHT, std::wregex::icase), L"");
   hResult = pDWFactory->CreateTextFormat(
       _mainFontFace.c_str(), NULL, fontWeight, fontStyle,
-      DWRITE_FONT_STRETCH_NORMAL, font_point * dpiScaleX_, L"",
+      DWRITE_FONT_STRETCH_NORMAL, font_point * dpiScaleFontPoint, L"",
       reinterpret_cast<IDWriteTextFormat**>(pPreeditTextFormat.GetAddressOf()));
   if (pPreeditTextFormat != NULL) {
     if (vertical_text) {
@@ -199,7 +199,7 @@ HRESULT DirectWriteResources::InitResources(
                          std::wregex(STYLEORWEIGHT, std::wregex::icase), L"");
   hResult = pDWFactory->CreateTextFormat(
       _mainFontFace.c_str(), NULL, fontWeight, fontStyle,
-      DWRITE_FONT_STRETCH_NORMAL, label_font_point * dpiScaleX_, L"",
+      DWRITE_FONT_STRETCH_NORMAL, label_font_point * dpiScaleFontPoint, L"",
       reinterpret_cast<IDWriteTextFormat**>(pLabelTextFormat.GetAddressOf()));
   if (pLabelTextFormat != NULL) {
     if (vertical_text) {
@@ -228,7 +228,7 @@ HRESULT DirectWriteResources::InitResources(
                          std::wregex(STYLEORWEIGHT, std::wregex::icase), L"");
   hResult = pDWFactory->CreateTextFormat(
       _mainFontFace.c_str(), NULL, fontWeight, fontStyle,
-      DWRITE_FONT_STRETCH_NORMAL, comment_font_point * dpiScaleX_, L"",
+      DWRITE_FONT_STRETCH_NORMAL, comment_font_point * dpiScaleFontPoint, L"",
       reinterpret_cast<IDWriteTextFormat**>(pCommentTextFormat.GetAddressOf()));
   if (pCommentTextFormat != NULL) {
     if (vertical_text) {
@@ -255,8 +255,8 @@ HRESULT DirectWriteResources::InitResources(const UIStyle& style,
                                             const UINT& dpi = 96) {
   _style = style;
   if (dpi) {
-    dpiScaleX_ = dpi / 72.0f;
-    dpiScaleY_ = dpi / 72.0f;
+    dpiScaleFontPoint = dpi / 72.0f;
+    dpiScaleLayout = dpi / 96.0f;
   }
   return InitResources(style.label_font_face, style.label_font_point,
                        style.font_face, style.font_point,
@@ -265,8 +265,8 @@ HRESULT DirectWriteResources::InitResources(const UIStyle& style,
 }
 
 void weasel::DirectWriteResources::SetDpi(const UINT& dpi) {
-  dpiScaleX_ = dpi / 72.0f;
-  dpiScaleY_ = dpi / 72.0f;
+  dpiScaleFontPoint = dpi / 72.0f;
+  dpiScaleLayout = dpi / 96.0f;
 
   pPreeditTextFormat.Reset();
   pTextFormat.Reset();
diff --git a/WeaselUI/FullScreenLayout.cpp b/WeaselUI/FullScreenLayout.cpp
index 9c86db40f..a5744dc22 100644
--- a/WeaselUI/FullScreenLayout.cpp
+++ b/WeaselUI/FullScreenLayout.cpp
@@ -92,17 +92,18 @@ bool FullScreenLayout::AdjustFontPoint(CDCHandle dc,
     int fontPointComment;
 
     if (pDWR->pLabelTextFormat != NULL)
-      fontPointLabel =
-          (int)(pDWR->pLabelTextFormat->GetFontSize() / pDWR->dpiScaleX_);
+      fontPointLabel = (int)(pDWR->pLabelTextFormat->GetFontSize() /
+                             pDWR->dpiScaleFontPoint);
     else
       fontPointLabel = 0;
     if (pDWR->pTextFormat != NULL)
-      fontPoint = (int)(pDWR->pTextFormat->GetFontSize() / pDWR->dpiScaleX_);
+      fontPoint =
+          (int)(pDWR->pTextFormat->GetFontSize() / pDWR->dpiScaleFontPoint);
     else
       fontPoint = 0;
     if (pDWR->pCommentTextFormat != NULL)
-      fontPointComment =
-          (int)(pDWR->pCommentTextFormat->GetFontSize() / pDWR->dpiScaleX_);
+      fontPointComment = (int)(pDWR->pCommentTextFormat->GetFontSize() /
+                               pDWR->dpiScaleFontPoint);
     else
       fontPointComment = 0;
     CSize sz = m_layout->GetContentSize();
diff --git a/WeaselUI/FullScreenLayout.h b/WeaselUI/FullScreenLayout.h
index aee24f4d0..27c43d5c1 100644
--- a/WeaselUI/FullScreenLayout.h
+++ b/WeaselUI/FullScreenLayout.h
@@ -9,8 +9,9 @@ class FullScreenLayout : public StandardLayout {
                    const Context& context,
                    const Status& status,
                    const CRect& inputPos,
-                   Layout* layout)
-      : StandardLayout(style, context, status),
+                   Layout* layout,
+                   PDWR pDWR)
+      : StandardLayout(style, context, status, pDWR),
         mr_inputPos(inputPos),
         m_layout(layout) {}
   virtual ~FullScreenLayout() { delete m_layout; }
diff --git a/WeaselUI/HorizontalLayout.h b/WeaselUI/HorizontalLayout.h
index eb5110ec4..11eb31dc4 100644
--- a/WeaselUI/HorizontalLayout.h
+++ b/WeaselUI/HorizontalLayout.h
@@ -7,8 +7,9 @@ class HorizontalLayout : public StandardLayout {
  public:
   HorizontalLayout(const UIStyle& style,
                    const Context& context,
-                   const Status& status)
-      : StandardLayout(style, context, status) {}
+                   const Status& status,
+                   PDWR pDWR)
+      : StandardLayout(style, context, status, pDWR) {}
   virtual void DoLayout(CDCHandle dc, PDWR pDWR = NULL);
 };
 };  // namespace weasel
diff --git a/WeaselUI/Layout.cpp b/WeaselUI/Layout.cpp
index 559957607..60b60b4d7 100644
--- a/WeaselUI/Layout.cpp
+++ b/WeaselUI/Layout.cpp
@@ -4,7 +4,8 @@ using namespace weasel;
 
 Layout::Layout(const UIStyle& style,
                const Context& context,
-               const Status& status)
+               const Status& status,
+               PDWR pDWR)
     : _style(style),
       _context(context),
       _status(status),
@@ -13,15 +14,35 @@ Layout::Layout(const UIStyle& style,
       labels(_context.cinfo.labels),
       id(_context.cinfo.highlighted),
       candidates_count((int)candidates.size()),
-      real_margin_x((abs(_style.margin_x) > _style.hilite_padding_x)
-                        ? abs(_style.margin_x)
-                        : _style.hilite_padding_x),
-      real_margin_y((abs(_style.margin_y) > _style.hilite_padding_y)
-                        ? abs(_style.margin_y)
-                        : _style.hilite_padding_y),
       labelFontValid(!!(_style.label_font_point > 0)),
       textFontValid(!!(_style.font_point > 0)),
       cmtFontValid(!!(_style.comment_font_point > 0)) {
+  if (pDWR) {
+    float scale = pDWR->dpiScaleLayout;
+    _style.min_width = (int)(_style.min_width * scale);
+    _style.min_height = (int)(_style.min_height * scale);
+    _style.max_width = (int)(_style.max_width * scale);
+    _style.max_height = (int)(_style.max_height * scale);
+    _style.border = (int)(_style.border * scale);
+    _style.margin_x = (int)(_style.margin_x * scale);
+    _style.margin_y = (int)(_style.margin_y * scale);
+    _style.spacing = (int)(_style.spacing * scale);
+    _style.candidate_spacing = (int)(_style.candidate_spacing * scale);
+    _style.hilite_spacing = (int)(_style.hilite_spacing * scale);
+    _style.hilite_padding_x = (int)(_style.hilite_padding_x * scale);
+    _style.hilite_padding_y = (int)(_style.hilite_padding_y * scale);
+    _style.round_corner = (int)(_style.round_corner * scale);
+    _style.round_corner_ex = (int)(_style.round_corner_ex * scale);
+    _style.shadow_radius = (int)(_style.shadow_radius * scale);
+    _style.shadow_offset_x = (int)(_style.shadow_offset_x * scale);
+    _style.shadow_offset_y = (int)(_style.shadow_offset_y * scale);
+  }
+  real_margin_x = ((abs(_style.margin_x) > _style.hilite_padding_x)
+                       ? abs(_style.margin_x)
+                       : _style.hilite_padding_x);
+  real_margin_y = ((abs(_style.margin_y) > _style.hilite_padding_y)
+                       ? abs(_style.margin_y)
+                       : _style.hilite_padding_y);
   offsetX = offsetY = 0;
   if (_style.shadow_radius != 0) {
     offsetX = abs(_style.shadow_offset_x) + _style.shadow_radius * 2;
diff --git a/WeaselUI/Layout.h b/WeaselUI/Layout.h
index f363e8ed5..0e19fd378 100644
--- a/WeaselUI/Layout.h
+++ b/WeaselUI/Layout.h
@@ -64,7 +64,10 @@ struct IsToRoundStruct {
 
 class Layout {
  public:
-  Layout(const UIStyle& style, const Context& context, const Status& status);
+  Layout(const UIStyle& style,
+         const Context& context,
+         const Status& status,
+         PDWR pDWR);
 
   virtual void DoLayout(CDCHandle dc, PDWR pDWR = NULL) = 0;
   /* All points in this class is based on the content area */
@@ -104,9 +107,11 @@ class Layout {
   int mark_width = 4;
   int mark_gap = 8;
   int mark_height = 0;
+  int real_margin_x;
+  int real_margin_y;
+  UIStyle _style;
 
  protected:
-  const UIStyle& _style;
   const Context& _context;
   const Status& _status;
   const std::vector<Text>& candidates;
@@ -114,8 +119,6 @@ class Layout {
   const std::vector<Text>& labels;
   const int& id;
   const int candidates_count;
-  const int real_margin_x;
-  const int real_margin_y;
   const int labelFontValid;
   const int textFontValid;
   const int cmtFontValid;
diff --git a/WeaselUI/StandardLayout.h b/WeaselUI/StandardLayout.h
index 9fd504f56..b16cc05ae 100644
--- a/WeaselUI/StandardLayout.h
+++ b/WeaselUI/StandardLayout.h
@@ -14,8 +14,9 @@ class StandardLayout : public Layout {
  public:
   StandardLayout(const UIStyle& style,
                  const Context& context,
-                 const Status& status)
-      : Layout(style, context, status) {}
+                 const Status& status,
+                 PDWR pDWR)
+      : Layout(style, context, status, pDWR) {}
 
   /* Layout */
 
diff --git a/WeaselUI/VHorizontalLayout.h b/WeaselUI/VHorizontalLayout.h
index 140a34a97..66937e8cc 100644
--- a/WeaselUI/VHorizontalLayout.h
+++ b/WeaselUI/VHorizontalLayout.h
@@ -7,8 +7,9 @@ class VHorizontalLayout : public StandardLayout {
  public:
   VHorizontalLayout(const UIStyle& style,
                     const Context& context,
-                    const Status& status)
-      : StandardLayout(style, context, status) {}
+                    const Status& status,
+                    PDWR pDWR)
+      : StandardLayout(style, context, status, pDWR) {}
   virtual void DoLayout(CDCHandle dc, PDWR pDWR = NULL);
 
  private:
diff --git a/WeaselUI/VerticalLayout.h b/WeaselUI/VerticalLayout.h
index baed528e5..e4f85ee25 100644
--- a/WeaselUI/VerticalLayout.h
+++ b/WeaselUI/VerticalLayout.h
@@ -7,8 +7,9 @@ class VerticalLayout : public StandardLayout {
  public:
   VerticalLayout(const UIStyle& style,
                  const Context& context,
-                 const Status& status)
-      : StandardLayout(style, context, status) {}
+                 const Status& status,
+                 PDWR pDWR)
+      : StandardLayout(style, context, status, pDWR) {}
   virtual void DoLayout(CDCHandle dc, PDWR pDWR = NULL);
 };
 };  // namespace weasel
diff --git a/WeaselUI/WeaselPanel.cpp b/WeaselUI/WeaselPanel.cpp
index 8f67763ad..fbdca4563 100644
--- a/WeaselUI/WeaselPanel.cpp
+++ b/WeaselUI/WeaselPanel.cpp
@@ -80,8 +80,10 @@ WeaselPanel::WeaselPanel(weasel::UI& ui)
 
   HMONITOR hMonitor = MonitorFromRect(m_inputPos, MONITOR_DEFAULTTONEAREST);
   UINT dpiX = 96, dpiY = 96;
-  if (hMonitor)
+  if (hMonitor) {
     GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+    m_hMonitor = hMonitor;
+  }
   dpi = dpiX;
   _InitFontRes();
   m_ostyle = m_style;
@@ -108,19 +110,19 @@ void WeaselPanel::_CreateLayout() {
 
   Layout* layout = NULL;
   if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT) {
-    layout = new VHorizontalLayout(m_style, m_ctx, m_status);
+    layout = new VHorizontalLayout(m_style, m_ctx, m_status, pDWR);
   } else {
     if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL ||
         m_style.layout_type == UIStyle::LAYOUT_VERTICAL_FULLSCREEN) {
-      layout = new VerticalLayout(m_style, m_ctx, m_status);
+      layout = new VerticalLayout(m_style, m_ctx, m_status, pDWR);
     } else if (m_style.layout_type == UIStyle::LAYOUT_HORIZONTAL ||
                m_style.layout_type == UIStyle::LAYOUT_HORIZONTAL_FULLSCREEN) {
-      layout = new HorizontalLayout(m_style, m_ctx, m_status);
+      layout = new HorizontalLayout(m_style, m_ctx, m_status, pDWR);
     }
 
     if (IS_FULLSCREENLAYOUT(m_style)) {
-      layout =
-          new FullScreenLayout(m_style, m_ctx, m_status, m_inputPos, layout);
+      layout = new FullScreenLayout(m_style, m_ctx, m_status, m_inputPos,
+                                    layout, pDWR);
     }
   }
   m_layout = layout;
@@ -139,7 +141,8 @@ void WeaselPanel::Refresh() {
       (m_ctx.empty() && should_show_icon);
   // show schema menu status: schema_id == L".default"
   bool show_schema_menu = m_status.schema_id == L".default";
-  bool margin_negative = (m_style.margin_x < 0 || m_style.margin_y < 0);
+  bool margin_negative =
+      (DPI_SCALE(m_style.margin_x) < 0 || DPI_SCALE(m_style.margin_y) < 0);
   // when to hide_cadidates?
   // 1. margin_negative, and not in show tips mode( ascii switching / half-full
   // switching / simp-trad switching / error tips), and not in schema menu
@@ -185,6 +188,7 @@ void WeaselPanel::_InitFontRes(bool forced) {
   }
   m_ostyle = m_style;
   dpi = dpiX;
+  dpiScaleLayout = (float)dpi / 96.0f;
 }
 
 static HBITMAP CopyDCToBitmap(HDC hDC, LPRECT lpRect) {
@@ -272,7 +276,8 @@ LRESULT WeaselPanel::OnLeftClickedUp(UINT uMsg,
     CRect rect = m_layout->GetCandidateRect((int)m_ctx.cinfo.highlighted);
     if (m_istorepos)
       rect.OffsetRect(0, m_offsetys[m_ctx.cinfo.highlighted]);
-    rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+    rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                     DPI_SCALE(m_style.hilite_padding_y));
     if (rect.PtInRect(point)) {
       size_t i = m_ctx.cinfo.highlighted;
       if (_UICallback) {
@@ -306,32 +311,36 @@ LRESULT WeaselPanel::OnLeftClickedDown(UINT uMsg,
     CRect recth = m_layout->GetCandidateRect((int)m_ctx.cinfo.highlighted);
     if (m_istorepos)
       recth.OffsetRect(0, m_offsetys[m_ctx.cinfo.highlighted]);
-    recth.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+    recth.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                      DPI_SCALE(m_style.hilite_padding_y));
     // capture widow
     if (recth.PtInRect(point))
       _CaptureRect(recth);
     else {
       // if shadow_color transparent, decrease the capture rectangle size
       if (COLORTRANSPARENT(m_style.shadow_color) &&
-          m_style.shadow_radius != 0) {
+          DPI_SCALE(m_style.shadow_radius) != 0) {
         CRect crc(rcw);
-        int shadow_gap =
-            (m_style.shadow_offset_x == 0 && m_style.shadow_offset_y == 0)
-                ? 2 * m_style.shadow_radius
-                : m_style.shadow_radius + m_style.shadow_radius / 2;
-        int ofx = m_style.hilite_padding_x + abs(m_style.shadow_offset_x) +
+        int shadow_gap = (DPI_SCALE(m_style.shadow_offset_x) == 0 &&
+                          DPI_SCALE(m_style.shadow_offset_y) == 0)
+                             ? 2 * DPI_SCALE(m_style.shadow_radius)
+                             : DPI_SCALE(m_style.shadow_radius) +
+                                   DPI_SCALE(m_style.shadow_radius) / 2;
+        int ofx = DPI_SCALE(m_style.hilite_padding_x) +
+                              abs(DPI_SCALE(m_style.shadow_offset_x)) +
                               shadow_gap >
-                          abs(m_style.margin_x)
-                      ? m_style.hilite_padding_x +
-                            abs(m_style.shadow_offset_x) + shadow_gap -
-                            abs(m_style.margin_x)
+                          abs(DPI_SCALE(m_style.margin_x))
+                      ? DPI_SCALE(m_style.hilite_padding_x) +
+                            abs(DPI_SCALE(m_style.shadow_offset_x)) +
+                            shadow_gap - abs(DPI_SCALE(m_style.margin_x))
                       : 0;
-        int ofy = m_style.hilite_padding_y + abs(m_style.shadow_offset_y) +
+        int ofy = DPI_SCALE(m_style.hilite_padding_y) +
+                              abs(DPI_SCALE(m_style.shadow_offset_y)) +
                               shadow_gap >
-                          abs(m_style.margin_y)
-                      ? m_style.hilite_padding_y +
-                            abs(m_style.shadow_offset_y) + shadow_gap -
-                            abs(m_style.margin_y)
+                          abs(DPI_SCALE(m_style.margin_y))
+                      ? DPI_SCALE(m_style.hilite_padding_y) +
+                            abs(DPI_SCALE(m_style.shadow_offset_y)) +
+                            shadow_gap - abs(DPI_SCALE(m_style.margin_y))
                       : 0;
         crc.DeflateRect(m_layout->offsetX - ofx, m_layout->offsetY - ofy);
         _CaptureRect(crc);
@@ -376,7 +385,8 @@ LRESULT WeaselPanel::OnLeftClickedDown(UINT uMsg,
       CRect rect = m_layout->GetCandidateRect((int)i);
       if (m_istorepos)
         rect.OffsetRect(0, m_offsetys[i]);
-      rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+      rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                       DPI_SCALE(m_style.hilite_padding_y));
       if (rect.PtInRect(point)) {
         bar_scale_ = 0.8f;
         // modify highlighted
@@ -435,7 +445,8 @@ LRESULT WeaselPanel::OnMouseMove(UINT uMsg,
     CRect rect = m_layout->GetCandidateRect((int)i);
     if (m_istorepos)
       rect.OffsetRect(0, m_offsetys[i]);
-    rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+    rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                     DPI_SCALE(m_style.hilite_padding_y));
     if (rect.PtInRect(point)) {
       if (i != m_ctx.cinfo.highlighted) {
         if (m_style.hover_type == UIStyle::HoverType::HILITE) {
@@ -486,7 +497,8 @@ void WeaselPanel::_HighlightText(CDCHandle& dc,
       NOT_FULLSCREENLAYOUT(m_style))
     hiliteBackPath = new GraphicsRoundRectPath(
         rc,
-        m_style.round_corner_ex - (m_style.border % 2 ? m_style.border / 2 : 0),
+        DPI_SCALE(m_style.round_corner_ex) -
+            (DPI_SCALE(m_style.border) % 2 ? DPI_SCALE(m_style.border) / 2 : 0),
         rd.IsTopLeftNeedToRound, rd.IsTopRightNeedToRound,
         rd.IsBottomRightNeedToRound, rd.IsBottomLeftNeedToRound);
   else  // background or current candidate background not out of window
@@ -494,12 +506,12 @@ void WeaselPanel::_HighlightText(CDCHandle& dc,
     hiliteBackPath = new GraphicsRoundRectPath(rc, radius);
 
   // 必须shadow_color都是非完全透明色才做绘制, 全屏状态不绘制阴影保证响应速度
-  if (m_style.shadow_radius && COLORNOTTRANSPARENT(shadowColor) &&
+  if (DPI_SCALE(m_style.shadow_radius) && COLORNOTTRANSPARENT(shadowColor) &&
       NOT_FULLSCREENLAYOUT(m_style)) {
-    CRect rect(blurMarginX + m_style.shadow_offset_x,
-               blurMarginY + m_style.shadow_offset_y,
-               rc.Width() + blurMarginX + m_style.shadow_offset_x,
-               rc.Height() + blurMarginY + m_style.shadow_offset_y);
+    CRect rect(blurMarginX + DPI_SCALE(m_style.shadow_offset_x),
+               blurMarginY + DPI_SCALE(m_style.shadow_offset_y),
+               rc.Width() + blurMarginX + DPI_SCALE(m_style.shadow_offset_x),
+               rc.Height() + blurMarginY + DPI_SCALE(m_style.shadow_offset_y));
     BYTE r = GetRValue(shadowColor);
     BYTE g = GetGValue(shadowColor);
     BYTE b = GetBValue(shadowColor);
@@ -513,16 +525,17 @@ void WeaselPanel::_HighlightText(CDCHandle& dc,
     Gdiplus::Graphics g_shadow(pBitmapDropShadow);
     g_shadow.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
     // dropshadow, draw a roundrectangle to blur
-    if (m_style.shadow_offset_x != 0 || m_style.shadow_offset_y != 0) {
+    if (DPI_SCALE(m_style.shadow_offset_x) != 0 ||
+        DPI_SCALE(m_style.shadow_offset_y) != 0) {
       GraphicsRoundRectPath shadow_path(rect, radius);
       Gdiplus::SolidBrush shadow_brush(shadow_color);
       g_shadow.FillPath(&shadow_brush, &shadow_path);
     }
     // round shadow, draw multilines as base round line
     else {
-      int step = alpha / m_style.shadow_radius / 2;
+      int step = alpha / DPI_SCALE(m_style.shadow_radius) / 2;
       Gdiplus::Pen pen_shadow(shadow_color, (Gdiplus::REAL)1);
-      for (int i = 0; i < m_style.shadow_radius; i++) {
+      for (int i = 0; i < DPI_SCALE(m_style.shadow_radius); i++) {
         GraphicsRoundRectPath round_path(rect, radius + 1 + i);
         g_shadow.DrawPath(&pen_shadow, &round_path);
         shadow_color = Gdiplus::Color::MakeARGB(alpha - i * step, r, g, b);
@@ -530,8 +543,8 @@ void WeaselPanel::_HighlightText(CDCHandle& dc,
         rect.InflateRect(1, 1);
       }
     }
-    DoGaussianBlur(pBitmapDropShadow, (float)m_style.shadow_radius,
-                   (float)m_style.shadow_radius);
+    DoGaussianBlur(pBitmapDropShadow, (float)DPI_SCALE(m_style.shadow_radius),
+                   (float)DPI_SCALE(m_style.shadow_radius));
 
     g_back.DrawImage(pBitmapDropShadow, rc.left - blurMarginX,
                      rc.top - blurMarginY);
@@ -548,12 +561,13 @@ void WeaselPanel::_HighlightText(CDCHandle& dc,
     g_back.FillPath(&back_brush, hiliteBackPath);
   }
   // draw border, for bordercolor not transparent and border valid
-  if (COLORNOTTRANSPARENT(bordercolor) && m_style.border > 0) {
+  if (COLORNOTTRANSPARENT(bordercolor) && DPI_SCALE(m_style.border) > 0) {
     Gdiplus::Color border_color = GDPCOLOR_FROM_COLORREF(bordercolor);
-    Gdiplus::Pen gPenBorder(border_color, (Gdiplus::REAL)m_style.border);
+    Gdiplus::Pen gPenBorder(border_color,
+                            (Gdiplus::REAL)DPI_SCALE(m_style.border));
     // candidate window border
     if (type == BackType::BACKGROUND) {
-      GraphicsRoundRectPath bgPath(rc, m_style.round_corner_ex);
+      GraphicsRoundRectPath bgPath(rc, DPI_SCALE(m_style.round_corner_ex));
       g_back.DrawPath(&gPenBorder, &bgPath);
     } else if (type !=
                BackType::TEXT)  // hilited_candidate_border / candidate_border
@@ -597,9 +611,9 @@ bool WeaselPanel::_DrawPreedit(const Text& text,
         _TextOut(rc_before, str_before.c_str(), str_before.length(),
                  m_style.text_color, txtFormat);
         if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT)
-          y += beforeSz.cy + m_style.hilite_spacing;
+          y += beforeSz.cy + DPI_SCALE(m_style.hilite_spacing);
         else
-          x += beforeSz.cx + m_style.hilite_spacing;
+          x += beforeSz.cx + DPI_SCALE(m_style.hilite_spacing);
       }
       {
         // zzz[yyy]
@@ -614,9 +628,9 @@ bool WeaselPanel::_DrawPreedit(const Text& text,
         _TextOut(rc_hi, str_highlight.c_str(), str_highlight.length(),
                  m_style.hilited_text_color, txtFormat);
         if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT)
-          y += rc_hi.Height() + m_style.hilite_spacing;
+          y += rc_hi.Height() + DPI_SCALE(m_style.hilite_spacing);
         else
-          x += rc_hi.Width() + m_style.hilite_spacing;
+          x += rc_hi.Width() + DPI_SCALE(m_style.hilite_spacing);
       }
       if (range.end < static_cast<int>(t.length())) {
         // zzz[yyy]xxx
@@ -680,9 +694,9 @@ bool WeaselPanel::_DrawPreeditBack(const Text& text,
 
       if (range.start > 0) {
         if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT)
-          y += beforeSz.cy + m_style.hilite_spacing;
+          y += beforeSz.cy + DPI_SCALE(m_style.hilite_spacing);
         else
-          x += beforeSz.cx + m_style.hilite_spacing;
+          x += beforeSz.cx + DPI_SCALE(m_style.hilite_spacing);
       }
       {
         CRect rc_hi;
@@ -702,15 +716,16 @@ bool WeaselPanel::_DrawPreeditBack(const Text& text,
             rc_hi.InflateRect((STATUS_ICON_SIZE - hilitedSz.cx) / 2, 0);
         }
 
-        rc_hi.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+        rc_hi.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                          DPI_SCALE(m_style.hilite_padding_y));
         IsToRoundStruct rd = m_layout->GetTextRoundInfo();
         if (m_istorepos) {
           std::swap(rd.IsTopLeftNeedToRound, rd.IsBottomLeftNeedToRound);
           std::swap(rd.IsTopRightNeedToRound, rd.IsBottomRightNeedToRound);
         }
         _HighlightText(dc, rc_hi, m_style.hilited_back_color,
-                       m_style.hilited_shadow_color, m_style.round_corner,
-                       BackType::TEXT, rd);
+                       m_style.hilited_shadow_color,
+                       DPI_SCALE(m_style.round_corner), BackType::TEXT, rd);
       }
     }
     drawn = true;
@@ -748,9 +763,10 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
           rect.OffsetRect(0, m_offsetys[i]);
           ReconfigRoundInfo(rd, i, m_candidateCount);
         }
-        rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+        rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                         DPI_SCALE(m_style.hilite_padding_y));
         _HighlightText(dc, rect, 0x00000000, m_style.candidate_shadow_color,
-                       m_style.round_corner, bkType, rd);
+                       DPI_SCALE(m_style.round_corner), bkType, rd);
         drawn = true;
       }
     }
@@ -766,9 +782,10 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
           rect.OffsetRect(0, m_offsetys[i]);
           ReconfigRoundInfo(rd, i, m_candidateCount);
         }
-        rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+        rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                         DPI_SCALE(m_style.hilite_padding_y));
         _HighlightText(dc, rect, m_style.candidate_back_color, 0x00000000,
-                       m_style.round_corner, bkType, rd,
+                       DPI_SCALE(m_style.round_corner), bkType, rd,
                        m_style.candidate_border_color);
         drawn = true;
       }
@@ -781,11 +798,12 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
         rect.OffsetRect(0, m_offsetys[m_hoverIndex]);
         ReconfigRoundInfo(rd, m_hoverIndex, m_candidateCount);
       }
-      rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+      rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                       DPI_SCALE(m_style.hilite_padding_y));
       _HighlightText(dc, rect,
                      HALF_ALPHA_COLOR(m_style.hilited_candidate_back_color),
                      HALF_ALPHA_COLOR(m_style.hilited_candidate_shadow_color),
-                     m_style.round_corner, bkType, rd,
+                     DPI_SCALE(m_style.round_corner), bkType, rd,
                      HALF_ALPHA_COLOR(m_style.hilited_candidate_border_color));
     }
     // draw highlighted background and shadow
@@ -797,17 +815,19 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
         rect.OffsetRect(0, m_offsetys[m_ctx.cinfo.highlighted]);
         ReconfigRoundInfo(rd, m_ctx.cinfo.highlighted, m_candidateCount);
       }
-      rect.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+      rect.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                       DPI_SCALE(m_style.hilite_padding_y));
       _HighlightText(dc, rect, m_style.hilited_candidate_back_color,
                      markSt ? m_style.hilited_candidate_shadow_color : 0,
-                     m_style.round_corner, bkType, rd,
+                     DPI_SCALE(m_style.round_corner), bkType, rd,
                      m_style.hilited_candidate_border_color);
       if (m_style.mark_text.empty() &&
           COLORNOTTRANSPARENT(m_style.hilited_mark_color)) {
-        int height = min(rect.Height() - m_style.hilite_padding_y * 2,
-                         rect.Height() - m_style.round_corner * 2);
-        int width = min(rect.Width() - m_style.hilite_padding_x * 2,
-                        rect.Width() - m_style.round_corner * 2);
+        int height =
+            min(rect.Height() - DPI_SCALE(m_style.hilite_padding_y) * 2,
+                rect.Height() - DPI_SCALE(m_style.round_corner) * 2);
+        int width = min(rect.Width() - DPI_SCALE(m_style.hilite_padding_x) * 2,
+                        rect.Width() - DPI_SCALE(m_style.round_corner) * 2);
         width = min(width, static_cast<int>(rect.Width() * 0.618));
         height = min(height, static_cast<int>(rect.Height() * 0.618));
         if (bar_scale_ != 1.0f) {
@@ -887,7 +907,8 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
         CRect rc = m_layout->GetHighlightRect();
         if (m_istorepos)
           rc.OffsetRect(0, m_offsetys[m_ctx.cinfo.highlighted]);
-        rc.InflateRect(m_style.hilite_padding_x, m_style.hilite_padding_y);
+        rc.InflateRect(DPI_SCALE(m_style.hilite_padding_x),
+                       DPI_SCALE(m_style.hilite_padding_y));
         int vgap = m_layout->mark_height
                        ? (rc.Height() - m_layout->mark_height) / 2
                        : 0;
@@ -895,15 +916,17 @@ bool WeaselPanel::_DrawCandidates(CDCHandle& dc, bool back) {
             m_layout->mark_width ? (rc.Width() - m_layout->mark_width) / 2 : 0;
         CRect hlRc;
         if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT)
-          hlRc =
-              CRect(rc.left + hgap, rc.top + m_style.hilite_padding_y,
-                    rc.left + hgap + m_layout->mark_width,
-                    rc.top + m_style.hilite_padding_y + m_layout->mark_height);
+          hlRc = CRect(rc.left + hgap,
+                       rc.top + DPI_SCALE(m_style.hilite_padding_y),
+                       rc.left + hgap + m_layout->mark_width,
+                       rc.top + DPI_SCALE(m_style.hilite_padding_y) +
+                           m_layout->mark_height);
         else
-          hlRc =
-              CRect(rc.left + m_style.hilite_padding_x, rc.top + vgap,
-                    rc.left + m_style.hilite_padding_x + m_layout->mark_width,
-                    rc.bottom - vgap);
+          hlRc = CRect(rc.left + DPI_SCALE(m_style.hilite_padding_x),
+                       rc.top + vgap,
+                       rc.left + DPI_SCALE(m_style.hilite_padding_x) +
+                           m_layout->mark_width,
+                       rc.bottom - vgap);
         _TextOut(hlRc, m_style.mark_text.c_str(), m_style.mark_text.length(),
                  m_style.hilited_mark_color, pDWR->pTextFormat.Get());
       }
@@ -956,7 +979,7 @@ void WeaselPanel::DoPaint(CDCHandle dc) {
               btmys[m_candidateCount - i - 1] - base_gap - rects[i].bottom;
         else
           m_offsetys[i] = (rects[i - 1].top + m_offsetys[i - 1] -
-                           m_style.candidate_spacing) -
+                           DPI_SCALE(m_style.candidate_spacing)) -
                           rects[i].bottom;
       }
       delete[] rects;
@@ -966,9 +989,8 @@ void WeaselPanel::DoPaint(CDCHandle dc) {
     if ((!m_ctx.empty() && !m_style.inline_preedit) ||
         (m_style.inline_preedit && (m_candidateCount || !m_ctx.aux.empty()))) {
       CRect backrc = m_layout->GetContentRect();
-      // if (!m_style.border) backrc.InflateRect(1,1);
       _HighlightText(memDC, backrc, m_style.back_color, m_style.shadow_color,
-                     m_style.round_corner_ex, BackType::BACKGROUND,
+                     DPI_SCALE(m_style.round_corner_ex), BackType::BACKGROUND,
                      IsToRoundStruct(), m_style.border_color);
     }
     if (!m_ctx.aux.str.empty()) {
@@ -1089,6 +1111,7 @@ LRESULT WeaselPanel::OnDpiChanged(UINT uMsg,
 void WeaselPanel::MoveTo(RECT const& rc) {
   if (!m_layout)
     return;  // avoid handling nullptr in _RepositionWindow
+  m_redraw_by_monitor_change = false;
   // if ascii_tip_follow_cursor set, move tip icon to mouse cursor
   if (m_style.ascii_tip_follow_cursor && m_ctx.empty() &&
       (!m_status.composing) && m_layout->ShouldDisplayStatusIcon()) {
@@ -1113,7 +1136,7 @@ void WeaselPanel::MoveTo(RECT const& rc) {
     // m_istorepos status changed by _RepositionWindow, or tips to show,
     // redrawing is required
     if (m_istorepos != m_istorepos_buf || !m_ctx.aux.empty() ||
-        m_layout->ShouldDisplayStatusIcon())
+        m_layout->ShouldDisplayStatusIcon() || m_redraw_by_monitor_change)
       RedrawWindow();
   }
 }
@@ -1128,6 +1151,10 @@ void WeaselPanel::_RepositionWindow(const bool& adj) {
     if (GetMonitorInfo(hMonitor, &info)) {
       rcWorkArea = info.rcWork;
     }
+    if (hMonitor != m_hMonitor) {
+      m_hMonitor = hMonitor;
+      m_redraw_by_monitor_change = true;
+    }
   }
   RECT rcWindow;
   GetWindowRect(&rcWindow);
@@ -1138,13 +1165,13 @@ void WeaselPanel::_RepositionWindow(const bool& adj) {
   rcWorkArea.bottom -= height;
   int x = m_inputPos.left;
   int y = m_inputPos.bottom;
-  if (m_style.shadow_radius) {
-    x -=
-        (m_style.shadow_offset_x >= 0 || COLORTRANSPARENT(m_style.shadow_color))
-            ? m_layout->offsetX
-            : (m_layout->offsetX / 2);
+  if (DPI_SCALE(m_style.shadow_radius)) {
+    x -= (DPI_SCALE(m_style.shadow_offset_x) >= 0 ||
+          COLORTRANSPARENT(m_style.shadow_color))
+             ? m_layout->offsetX
+             : (m_layout->offsetX / 2);
     if (adj)
-      y -= (m_style.shadow_offset_y > 0 ||
+      y -= (DPI_SCALE(m_style.shadow_offset_y) > 0 ||
             COLORTRANSPARENT(m_style.shadow_color))
                ? m_layout->offsetY
                : (m_layout->offsetY / 2);
@@ -1153,7 +1180,7 @@ void WeaselPanel::_RepositionWindow(const bool& adj) {
   if (m_style.layout_type == UIStyle::LAYOUT_VERTICAL_TEXT &&
       !m_style.vertical_text_left_to_right) {
     x += m_layout->offsetX - width;
-    if (m_style.shadow_offset_x < 0)
+    if (DPI_SCALE(m_style.shadow_offset_x) < 0)
       x += m_layout->offsetX;
   }
   if (adj)
@@ -1167,12 +1194,13 @@ void WeaselPanel::_RepositionWindow(const bool& adj) {
     if (!m_sticky)
       m_sticky = true;
     y = m_inputPos.top - height - 6;  // over workarea bottom
-    if (m_style.shadow_radius && m_style.shadow_offset_y > 0)
-      y -= m_style.shadow_offset_y;
+    if (DPI_SCALE(m_style.shadow_radius) &&
+        DPI_SCALE(m_style.shadow_offset_y) > 0)
+      y -= DPI_SCALE(m_style.shadow_offset_y);
     m_istorepos = (m_style.vertical_auto_reverse &&
                    m_style.layout_type == UIStyle::LAYOUT_VERTICAL);
-    if (m_style.shadow_radius > 0)
-      y += (m_style.shadow_offset_y < 0 ||
+    if (DPI_SCALE(m_style.shadow_radius) > 0)
+      y += (DPI_SCALE(m_style.shadow_offset_y) < 0 ||
             COLORTRANSPARENT(m_style.shadow_color))
                ? m_layout->offsetY
                : (m_layout->offsetY / 2);
diff --git a/WeaselUI/WeaselPanel.h b/WeaselUI/WeaselPanel.h
index 4670d45b2..512d73255 100644
--- a/WeaselUI/WeaselPanel.h
+++ b/WeaselUI/WeaselPanel.h
@@ -73,6 +73,10 @@ class WeaselPanel
   static UINT_PTR ptimer;
 
  private:
+  template <typename T>
+  int DPI_SCALE(T t) {
+    return (int)(t * dpiScaleLayout);
+  }
   void _InitFontRes(bool forced = false);
   void _CaptureRect(CRect& rect);
   bool m_mouse_entry = false;
@@ -137,5 +141,8 @@ class WeaselPanel
   std::function<void(size_t* const, size_t* const, bool* const, bool* const)>&
       _UICallback;
   float bar_scale_ = 1.0;
+  float dpiScaleLayout = 1.0f;
   int m_hoverIndex = -1;
+  HMONITOR m_hMonitor = NULL;
+  bool m_redraw_by_monitor_change = false;
 };
diff --git a/include/WeaselUI.h b/include/WeaselUI.h
index 6efd291a5..9a5382a69 100644
--- a/include/WeaselUI.h
+++ b/include/WeaselUI.h
@@ -150,7 +150,7 @@ class DirectWriteResources {
   void SetBrushColor(const D2D1_COLOR_F& color) { pBrush->SetColor(color); }
   void SetDpi(const UINT& dpi);
 
-  float dpiScaleX_, dpiScaleY_;
+  float dpiScaleFontPoint, dpiScaleLayout;
   ComPtr<ID2D1Factory> pD2d1Factory;
   ComPtr<IDWriteFactory2> pDWFactory;
   ComPtr<ID2D1DCRenderTarget> pRenderTarget;


Test patch: