From 169cc166addb9921b8ed52f769599f956a4d418d Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Wed, 6 Aug 2025 15:37:44 +0800 Subject: [PATCH 01/38] fix: wrong caret position in some electron apps --- Lib/GetCaretPosEx/GetCaretPosEx.patch | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Lib/GetCaretPosEx/GetCaretPosEx.patch b/Lib/GetCaretPosEx/GetCaretPosEx.patch index c412bee..331e1af 100644 --- a/Lib/GetCaretPosEx/GetCaretPosEx.patch +++ b/Lib/GetCaretPosEx/GetCaretPosEx.patch @@ -1,8 +1,8 @@ diff --git a/Lib/GetCaretPosEx/GetCaretPosEx.ahk b/Lib/GetCaretPosEx/GetCaretPosEx.ahk -index ff9a7f7..92f5109 100644 +index ff9a7f7..ac6cc79 100644 --- a/Lib/GetCaretPosEx/GetCaretPosEx.ahk +++ b/Lib/GetCaretPosEx/GetCaretPosEx.ahk -@@ -14,6 +14,11 @@ GetCaretPosEx(&left?, &top?, &right?, &bottom?, useHook := false) { +@@ -14,10 +14,17 @@ GetCaretPosEx(&left?, &top?, &right?, &bottom?, useHook := false) { className := WinGetClass(hwnd) catch className := "" @@ -14,7 +14,13 @@ index ff9a7f7..92f5109 100644 if className ~= "^(?:Windows|Microsoft)\.UI\..+" funcs := [getCaretPosFromUIA, getCaretPosFromHook, getCaretPosFromMSAA] else if className ~= "^HwndWrapper\[PowerShell_ISE\.exe;;[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\]" -@@ -332,8 +332,12 @@ end: + funcs := [getCaretPosFromHook, getCaretPosFromWpfCaret] ++ else if className ~= "^Chrome_WidgetWin_.+" ++ funcs := [getCaretPosFromUIA, getCaretPosFromHook] + else + funcs := [getCaretPosFromMSAA, getCaretPosFromUIA, getCaretPosFromHook] + for fn in funcs { +@@ -332,8 +339,12 @@ end: } static getWindowScale(hwnd) { From 5695364cce33179090bd9a1c67881bbca70749a5 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Wed, 6 Aug 2025 16:56:38 +0800 Subject: [PATCH 02/38] fix: incorrect log file names --- Lib/RabbitCommon.ahk | 1 + Rabbit.ahk | 2 +- RabbitDeployer.ahk | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/RabbitCommon.ahk b/Lib/RabbitCommon.ahk index e8ebf44..d69110d 100644 --- a/Lib/RabbitCommon.ahk +++ b/Lib/RabbitCommon.ahk @@ -46,6 +46,7 @@ global RABBIT_FULL_MAINTENANCE := "2" global IN_MAINTENANCE := false global STATUS_TOOLTIP := 2 global box := 0 +global rabbit_traits global IS_DARK_MODE := false global ASCII_MODE_FALSE_LABEL := "中文" global ASCII_MODE_TRUE_LABEL := "西文" diff --git a/Rabbit.ahk b/Rabbit.ahk index 88ea554..089901f 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -42,7 +42,7 @@ RabbitMain(A_Args) ; args[2]: deployer result ; args[3]: keyboard layout RabbitMain(args) { - global box + global box, rabbit_traits if args.Length >= 3 layout := Number(args[3]) if !IsSet(layout) || layout == 0 { diff --git a/RabbitDeployer.ahk b/RabbitDeployer.ahk index d9dad39..93d75cd 100644 --- a/RabbitDeployer.ahk +++ b/RabbitDeployer.ahk @@ -117,6 +117,7 @@ class Configurator extends Class { } Initialize() { + global rabbit_traits rabbit_traits := CreateTraits() rime.setup(rabbit_traits) rime.deployer_initialize(0) From 4189c412b480561b4d63def500a3f33e99882e2d Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Fri, 8 Aug 2025 18:14:29 +0800 Subject: [PATCH 03/38] feat: refactor candidate box Support more styles - comment_text_color - hilited_back_color - hilited_text_color - hilited_candidate_text_color - hilited_candidate_back_color - hilited_comment_text_color - margin_x, margin_y - min_width --- Lib/RabbitCandidateBox.ahk | 356 ++++++++++++++++++++++++++----------- Lib/RabbitTrayMenu.ahk | 5 +- Lib/RabbitUIStyle.ahk | 21 +++ Rabbit.ahk | 10 +- 4 files changed, 279 insertions(+), 113 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 32140ca..896d52a 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -16,112 +16,278 @@ * */ +#Include + global LVM_GETCOLUMNWIDTH := 0x101D ; https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles global WS_EX_NOACTIVATE := "+E0x8000000" +global WS_EX_COMPOSITED := "+E0x02000000" +global WS_EX_LAYERED := "+E0x00080000" -class CandidateBox extends Gui { - static min_width := 150 - static idx_col := 1 - static cand_col := 2 - static comment_col := 3 - static num_col := 3 +class CandidateBox { + static dbg := false + static gui := 0 + static back_color := 0xeeeeec + static text_color := 0x000000 + static font_face := "Microsoft YaHei UI" + static font_point := 12 + static comment_text_color := 0x222222 + static hilited_back_color := 0x000000 + static hilited_text_color := 0xffffff + static hilited_candidate_text_color := 0xffffff + static hilited_candidate_back_color := 0x000000 + static hilited_comment_text_color := 0x222222 + static margin_x := 5 + static margin_y := 5 + static min_width := 150 + static border := CandidateBox.dbg ? "+border" : 0 __New() { - super.__New(, , this) - local back_color_val := UIStyle.back_color & 0xffffff - local text_color := Format("c{:x}", UIStyle.text_color & 0xffffff) - local font_point := Format("S{:d}", UIStyle.font_point) - local font_face := UIStyle.font_face - this.Opt("-DPIScale -Caption +Owner AlwaysOnTop " . WS_EX_NOACTIVATE) - this.MarginX := 3 - this.MarginY := 3 - this.BackColor := back_color_val - this.SetFont(Format("{} {}", font_point, text_color), font_face) - - this.pre := this.AddText(, "p") - this.pre.GetPos(, , , &h) - this.preedit_height := h - this.lv := this.AddListView("-Multi -Hdr -E0x200 LV0x10000 cWhite Background0x191919", ["i", "c", "m"]) - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "DarkMode_Explorer", "Ptr", 0) - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "DarkMode_ItemsView", "ptr", 0) - - this.dummy_lv1 := this.AddListView("-Multi -Hdr -E0x200 LV0x40 Hidden R1", ["p"]) - this.dummy_lv2 := this.AddListView("-Multi -Hdr -E0x200 LV0x40 Hidden R2", ["p"]) - this.dummy_lv1.GetPos(, , , &dh1) - this.dummy_lv2.GetPos(, , , &dh2) - this.row_height := dh2 - dh1 - this.row_padding := dh1 - this.row_height + this.UpdateUIStyle() } UpdateUIStyle() { - local back_color_val := UIStyle.back_color & 0xffffff ; alpha not supported - local text_color := Format("c{:x}", UIStyle.text_color & 0xffffff) - local font_point := Format("S{:d}", UIStyle.font_point) - local font_face := UIStyle.font_face - this.BackColor := back_color_val - this.SetFont(Format("{} {}", font_point, text_color), font_face) - this.pre.SetFont(Format("{} {}", font_point, text_color), font_face) - this.lv.Opt(Format("{} Background0x{:x}", text_color, back_color_val)) - - if UIStyle.use_dark { - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "DarkMode_Explorer", "ptr", 0) - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "DarkMode_ItemsView", "ptr", 0) - } else { - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "Explorer", "Ptr", 0) - DllCall("uxtheme\SetWindowTheme", "ptr", this.lv.hwnd, "WStr", "ItemsView", "Ptr", 0) + ; alpha not supported + del_opaque(color) { + return color & 0xffffff } + CandidateBox.back_color := del_opaque(UIStyle.back_color) + CandidateBox.text_color := del_opaque(UIStyle.text_color) + if UIStyle.font_face + CandidateBox.font_face := UIStyle.font_face + CandidateBox.font_point := UIStyle.font_point + CandidateBox.comment_text_color := del_opaque(UIStyle.comment_text_color) + CandidateBox.hilited_back_color := del_opaque(UIStyle.hilited_back_color) + CandidateBox.hilited_text_color := del_opaque(UIStyle.hilited_text_color) + CandidateBox.hilited_candidate_back_color := del_opaque(UIStyle.hilited_candidate_back_color) + CandidateBox.hilited_candidate_text_color := del_opaque(UIStyle.hilited_candidate_text_color) + CandidateBox.hilited_comment_text_color := del_opaque(UIStyle.hilited_comment_text_color) + CandidateBox.margin_x := UIStyle.margin_x + CandidateBox.margin_y := UIStyle.margin_y } Build(context, &width, &height) { - local has_selected := GetCompositionText(context.composition, &pre_selected, &selected, &post_selected) local cands := context.menu.candidates - local lv_height := this.row_height * context.menu.num_candidates + this.row_padding - - preedit_text := pre_selected - if has_selected - preedit_text := preedit_text . "[" . selected "]" . post_selected - - this.pre.Value := preedit_text - this.dummy_lv1.Delete() - this.dummy_lv1.Add(, preedit_text) - this.dummy_lv1.ModifyCol() - preedit_width := SendMessage(LVM_GETCOLUMNWIDTH, 0, 0, this.dummy_lv1) - - this.lv.Delete() - has_comment := false - Loop context.menu.num_candidates { - opt := (A_Index == context.menu.highlighted_candidate_index + 1) ? "Select" : "" - if comment := cands[A_Index].comment - has_comment := true - this.lv.Add(opt, A_Index . ". ", cands[A_Index].text, comment) + GetCompositionText(context.composition, &pre_selected, &selected, &post_selected) + if !CandidateBox.gui || !CandidateBox.gui.built { + CandidateBox.gui := CandidateBox.BoxGui( + pre_selected, + selected, + post_selected, + cands, + context.menu.num_candidates, + context.menu.highlighted_candidate_index + 1 + ) + } else { + CandidateBox.gui.Update( + pre_selected, + selected, + post_selected, + cands, + context.menu.num_candidates, + context.menu.highlighted_candidate_index + 1 + ) } + CandidateBox.gui.GetPos(, , &width, &height) + } + + Show(x, y) { + CandidateBox.gui.Show(Format("AutoSize NA x{} y{}", x, y)) + } + + Hide() { + if CandidateBox.gui && HasMethod(CandidateBox.gui, "Show") + CandidateBox.gui.Show("Hide") + } + + class BoxGui extends Gui { + built := false + __New(pre, sel, post, cands, num_candidates, hilited_index) { + super.__New(, , this) + this.Opt(Format("-DPIScale -Caption +Owner +AlwaysOnTop {} {} {}", WS_EX_NOACTIVATE, WS_EX_COMPOSITED, WS_EX_LAYERED)) + this.BackColor := CandidateBox.back_color + this.SetFont(Format("s{} c{:x}", CandidateBox.font_point, CandidateBox.text_color), CandidateBox.font_face) + this.MarginX := CandidateBox.margin_x + this.MarginY := CandidateBox.margin_y + this.num_candidates := num_candidates + this.has_comment := false + + local hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color) + + ; build preedit + this.max_width := 0 + this.preedit_height := 0 + local head_position := Format("x{} y{} section {}", this.MarginX, this.MarginY, CandidateBox.border) + local position := head_position + if pre { + this.pre := this.AddText(position, pre) + position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + this.pre.GetPos(, , &w, &h) + this.preedit_height := max(this.preedit_height, h) + this.pre_width := w + this.max_width += (w + this.MarginX) + } + if sel { + this.sel := this.AddText(position, sel) + this.sel.Opt(hilited_opt) + position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + this.sel.GetPos(, , &w, &h) + this.preedit_height := max(this.preedit_height, h) + this.sel_width := w + this.max_width += (w + this.MarginX) + } + if post { + this.post := this.AddText(position, post) + this.post.GetPos(, , &w, &h) + this.preedit_height := max(this.preedit_height, h) + this.post_width := w + this.max_width += w + } - total_width := 0 - this.lv.ModifyCol() - if not has_comment - this.lv.ModifyCol(CandidateBox.comment_col, 0) - this.lv.GetPos(, , , &cands_height) - Loop CandidateBox.num_col { - width := SendMessage(LVM_GETCOLUMNWIDTH, A_Index - 1, 0, this.lv) - total_width += width - if A_Index == CandidateBox.cand_col - cand_width := width + ; build candidates + this.max_label_width := 0 + this.max_candidate_width := 0 + this.max_comment_width := 0 + this.candidate_height := 0 + hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) + loop num_candidates { + position := Format("xs y+{} section {}", this.MarginY, CandidateBox.border) + local label := this.AddText(Format("Right {} vL{}", position, A_Index), A_Index . ". ") + label.GetPos(, , &w, &h1) + this.max_label_width := max(this.max_label_width, w + this.MarginX) + + position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + local candidate := this.AddText(Format("{} vC{}", position, A_Index), cands[A_Index].text) + candidate.GetPos(, , &w, &h2) + this.max_candidate_width := max(this.max_candidate_width, w + this.MarginX) + + if comment_text := cands[A_Index].comment + this.has_comment := true + local comment := this.AddText(Format("{} vM{}", position, A_Index), comment_text) + comment.GetPos(, , &w, &h3) + comment.Opt(Format("c{:x}", CandidateBox.comment_text_color)) + this.max_comment_width := max(this.max_comment_width, w) + this.candidate_height := max(this.candidate_height, h1, h2, h3) + + if A_Index == hilited_index { + label.Opt(hilited_opt) + candidate.Opt(hilited_opt) + comment.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color)) + } + } + + ; adjust width height + local list_width := this.max_label_width + this.max_candidate_width + this.has_comment * this.max_comment_width + local box_width := max(CandidateBox.min_width, list_width) + if box_width > this.max_width && HasProp(this, "post") && this.post + this.post.Move(, , this.post_width + box_width - this.max_width) + if box_width > list_width { + this.max_candidate_width += box_width - list_width + loop num_candidates + this["C" . A_Index].Move(, , this.max_candidate_width) + } + local y := 2 * this.MarginY + this.preedit_height + loop num_candidates { + local x := this.MarginX + this["L" . A_Index].Move(x, y, this.max_label_width) + this["L" . A_Index].GetPos(, , , &h) + local max_h := h + x += this.max_label_width + this["C" . A_Index].Move(x, y, this.max_candidate_width) + this["C" . A_Index].GetPos(, , , &h) + max_h := max(max_h, h) + x += this.max_candidate_width + this["M" . A_Index].Move(x, y, this.max_comment_width) + this["M" . A_Index].GetPos(, , , &h) + max_h := max(max_h, h) + y += (max_h + this.MarginY) + } + + this.built := true } - if not cand_width - cand_width := SendMessage(LVM_GETCOLUMNWIDTH, CandidateBox.cand_col - 1, 0, this.lv) + Update(pre, sel, post, cands, num_candidates, hilited_index) { + local fake_gui := CandidateBox.BoxGui(pre, sel, post, cands, num_candidates, hilited_index) - max_width := Max(preedit_width, total_width, CandidateBox.min_width) - if total_width < max_width - this.lv.ModifyCol(CandidateBox.cand_col, cand_width + max_width - total_width) + ; reset preedit + if pre { + if !HasProp(this, "pre") || !this.pre + this.pre := this.AddText(, pre) + this.pre.Value := fake_gui.pre.Value + fake_gui.pre.GetPos(&x, &y, &w, &h) + this.pre.Move(x, y, w, h) + } + if HasProp(this, "pre") && this.pre + this.pre.Visible := !!pre + if sel { + if !HasProp(this, "sel") || !this.sel + this.sel := this.AddText(, sel) + this.sel.Value := fake_gui.sel.Value + fake_gui.sel.GetPos(&x, &y, &w, &h) + this.sel.Move(x, y, w, h) + } + if HasProp(this, "sel") && this.sel + this.sel.Visible := !!sel + if post { + if !HasProp(this, "post") || !this.post + this.post := this.AddText(, post) + this.post.Value := fake_gui.post.Value + fake_gui.post.GetPos(&x, &y, &w, &h) + this.post.Move(x, y, w, h) + } + if HasProp(this, "post") && this.post + this.post.Visible := !!post - this.lv.Move(, , max_width, lv_height) - this.pre.Move(, , max_width) - this.lv.Redraw() + ; reset candidates + hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) + normal_opt := Format("c{:x} Background{:x}", CandidateBox.text_color, CandidateBox.back_color) + loop this.num_candidates { + if A_Index > num_candidates { + this["L" . A_Index].Visible := false + this["C" . A_Index].Visible := false + this["M" . A_Index].Visible := false + continue + } + local fake_label := fake_gui["L" . A_Index] + local fake_candidate := fake_gui["C" . A_Index] + local fake_comment := fake_gui["M" . A_Index] + if this.num_candidates < A_Index { + this.AddText(Format("vL{}", A_Index), fake_label.Value) + this.AddText(Format("vC{}", A_Index), fake_candidate.Value) + this.AddText(Format("vM{}", A_Index), fake_comment.Value) + } + local label := this["L" . A_Index] + local candidate := this["C" . A_Index] + local comment := this["M" . A_Index] + label.Value := fake_label.Value + fake_label.GetPos(&x, &y, &w, &h) + label.Move(x, y, w, h) + candidate.Value := fake_candidate.Value + fake_candidate.GetPos(&x, &y, &w, &h) + candidate.Move(x, y, w, h) + comment.Value := fake_comment.Value + fake_comment.GetPos(&x, &y, &w, &h) + comment.Move(x, y, w, h) - width := max_width + 6 - height := this.preedit_height + lv_height + this.MarginY + if A_Index == hilited_index { + label.Opt(hilited_opt) + candidate.Opt(hilited_opt) + comment.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color)) + } else { + label.Opt(normal_opt) + candidate.Opt(normal_opt) + comment.Opt(Format("c{:x} Background{:x}", CandidateBox.comment_text_color, CandidateBox.back_color)) + } + local visible := (A_Index <= num_candidates) + label.Visible := visible + candidate.Visible := visible + comment.Visible := visible + } + this.num_candidates := max(this.num_candidates, num_candidates) + + fake_gui.GetPos(, , &width, &height) + this.Move(, , width, height) + } } } @@ -178,23 +344,3 @@ GetCompositionText(composition, &pre_selected, &selected, &post_selected) { return false } } - -GetMenuText(menu) { - local text := "" - if menu.num_candidates == 0 - return text - local cands := menu.candidates - Loop menu.num_candidates { - local is_highlighted := (A_Index == menu.highlighted_candidate_index + 1) - if A_Index > 1 - text := text . "`r`n" - text := text . Format("{}. {}{}{}{}", - A_Index, - (is_highlighted ? "[" : " "), - cands[A_Index].text, - (is_highlighted ? "]" : " "), - cands[A_Index].comment - ) - } - return text -} diff --git a/Lib/RabbitTrayMenu.ahk b/Lib/RabbitTrayMenu.ahk index 8af8e8b..e4fa913 100644 --- a/Lib/RabbitTrayMenu.ahk +++ b/Lib/RabbitTrayMenu.ahk @@ -84,9 +84,8 @@ RunDeployer(cmd, argv*) { ToggleSuspend() { global rime, session_id, box, STATUS_TOOLTIP - ToolTip() - if box - box.Show("Hide") + if box && HasMethod(box, "Hide") + box.Hide() rime.clear_composition(session_id) Suspend(-1) UpdateTrayTip() diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index 7777f8f..4ecc69d 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -24,6 +24,15 @@ class UIStyle { static back_color := 0xffeceeee static font_face := "Microsoft YaHei UI" static font_point := 12 + static comment_text_color := 0xff222222 + static hilited_back_color := 0xff000000 + static hilited_text_color := 0xffffffff + static hilited_candidate_text_color := 0xffffffff + static hilited_candidate_back_color := 0xff000000 + static hilited_comment_text_color := 0xff222222 + static margin_x := 5 + static margin_y := 5 + static min_width := 150 static Update(config, initialize) { global rime @@ -36,6 +45,12 @@ class UIStyle { UIStyle.font_point := rime.config_get_int(config, "style/font_point") if UIStyle.font_point <= 0 UIStyle.font_point := 12 + if rime.config_test_get_int(config, "style/layout/margin_x", &mx) && mx >= 0 + UIStyle.margin_x := mx + if rime.config_test_get_int(config, "style/layout/margin_y", &my) && my >= 0 + UIStyle.margin_y := my + if rime.config_test_get_int(config, "style/layout/min_width", &w) && w >= 0 + UIStyle.min_width := w if initialize and color := rime.config_get_string(config, "style/color_scheme") UIStyle.UpdateColor(config, color) } @@ -52,6 +67,12 @@ class UIStyle { UIStyle.text_color := UIStyle.GetColor(config, prefix . "/text_color", fmt, 0xff000000) UIStyle.back_color := UIStyle.GetColor(config, prefix . "/back_color", fmt, 0xffeceeee) + UIStyle.comment_text_color := UIStyle.GetColor(config, prefix . "/comment_text_color", fmt, UIStyle.text_color) + UIStyle.hilited_text_color := UIStyle.GetColor(config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) + UIStyle.hilited_back_color := UIStyle.GetColor(config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) + UIStyle.hilited_candidate_text_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) + UIStyle.hilited_candidate_back_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) + UIStyle.hilited_comment_text_color := UIStyle.GetColor(config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_candidate_text_color) return true } diff --git a/Rabbit.ahk b/Rabbit.ahk index 089901f..2e0c55e 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -357,7 +357,7 @@ ProcessKey(key, mask, this_hotkey) { else last_is_hide := false SendText(commit.text) - box.Show("Hide") + box.Hide() rime.free_commit(commit) } else last_is_hide := false @@ -386,7 +386,7 @@ ProcessKey(key, mask, this_hotkey) { show_at_left_top := !!info if show_at_left_top && !last_is_hide { box.Build(context, &box_width, &box_height) - box.Show("AutoSize NA x" . info.work.left + 4 . " y" . info.work.top + 4) + box.Show(info.work.left + 4, info.work.top + 4) } } if !show_at_left_top && GetCaretPos(&caret_x, &caret_y, &caret_w, &caret_h) { @@ -416,7 +416,7 @@ ProcessKey(key, mask, this_hotkey) { } } if !last_is_hide - box.Show("AutoSize NA x" . new_x . " y" . new_y) + box.Show(new_x, new_y) prev_x := new_x prev_y := new_y } else if !show_at_left_top { @@ -425,11 +425,11 @@ ProcessKey(key, mask, this_hotkey) { MouseGetPos(&mouse_x, &mouse_y) CoordMode("Mouse", backup_mouse_ref) box.Build(context, &box_width, &box_height) - box.Show("AutoSize NA x" . mouse_x . " y" . mouse_y) + box.Show(mouse_x, mouse_y) } prev_show := true } else { - box.Show("Hide") + box.Hide() prev_show := false } rime.free_context(context) From ef09885b2d4291d07272104d630e93be1012c99a Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 9 Aug 2025 08:49:22 +0800 Subject: [PATCH 04/38] fix: color not update on dark mode switching --- Lib/RabbitCandidateBox.ahk | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 896d52a..24e7835 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -64,6 +64,20 @@ class CandidateBox { CandidateBox.hilited_comment_text_color := del_opaque(UIStyle.hilited_comment_text_color) CandidateBox.margin_x := UIStyle.margin_x CandidateBox.margin_y := UIStyle.margin_y + + if CandidateBox.gui { + CandidateBox.gui.BackColor := CandidateBox.back_color + CandidateBox.gui.SetFont(Format("s{} c{:x}", CandidateBox.font_point, CandidateBox.text_color), CandidateBox.font_face) + CandidateBox.gui.MarginX := CandidateBox.margin_x + CandidateBox.gui.MarginY := CandidateBox.margin_y + + if HasProp(CandidateBox.gui, "pre") && CandidateBox.gui.pre + CandidateBox.gui.pre.Opt(Format("c{:x}", CandidateBox.text_color)) + if HasProp(CandidateBox.gui, "sel") && CandidateBox.gui.sel + CandidateBox.gui.sel.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color)) + if HasProp(CandidateBox.gui, "post") && CandidateBox.gui.post + CandidateBox.gui.post.Opt(Format("c{:x}", CandidateBox.text_color)) + } } Build(context, &width, &height) { From f29287fbdfeae2ec23022a47aa13e59e4e74b29d Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 9 Aug 2025 08:49:39 +0800 Subject: [PATCH 05/38] feat: tray menu support dark mode --- Lib/RabbitConfig.ahk | 1 + Lib/RabbitUIStyle.ahk | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Lib/RabbitConfig.ahk b/Lib/RabbitConfig.ahk index b50c9ca..056d96a 100644 --- a/Lib/RabbitConfig.ahk +++ b/Lib/RabbitConfig.ahk @@ -63,6 +63,7 @@ class RabbitConfig { if IS_DARK_MODE := RabbitIsUserDarkMode() { if color_name := rime.config_get_string(config, "style/color_scheme_dark") UIStyle.use_dark := UIStyle.UpdateColor(config, color_name) + DarkMode.set(IS_DARK_MODE) } rime.config_close(config) diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index 4ecc69d..6b60309 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -164,5 +164,14 @@ OnColorChange(wParam, lParam, msg, hWnd) { rime.config_close(config) box.UpdateUIStyle() } + DarkMode.set(IS_DARK_MODE) + } +} + +; https://www.autohotkey.com/boards/viewtopic.php?p=515002&sid=859605067314b6d823a026658547b66f#p515002 +class DarkMode { + static set(mode) { + DllCall(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "uxtheme", "ptr"), "ptr", 135, "ptr"), "int", mode) + DllCall(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "uxtheme", "ptr"), "ptr", 136, "ptr")) } } From 3f2b04a1f4b584e9b27e49c663fe0a05006804be Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 9 Aug 2025 10:37:31 +0800 Subject: [PATCH 06/38] fix: throw error when num candidates not match --- Lib/RabbitCandidateBox.ahk | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 24e7835..77a723a 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -255,6 +255,7 @@ class CandidateBox { ; reset candidates hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) normal_opt := Format("c{:x} Background{:x}", CandidateBox.text_color, CandidateBox.back_color) + this.num_candidates := max(this.num_candidates, num_candidates) loop this.num_candidates { if A_Index > num_candidates { this["L" . A_Index].Visible := false @@ -265,14 +266,18 @@ class CandidateBox { local fake_label := fake_gui["L" . A_Index] local fake_candidate := fake_gui["C" . A_Index] local fake_comment := fake_gui["M" . A_Index] - if this.num_candidates < A_Index { - this.AddText(Format("vL{}", A_Index), fake_label.Value) - this.AddText(Format("vC{}", A_Index), fake_candidate.Value) - this.AddText(Format("vM{}", A_Index), fake_comment.Value) - } - local label := this["L" . A_Index] - local candidate := this["C" . A_Index] - local comment := this["M" . A_Index] + try + local label := this["L" . A_Index] + catch + local label := this.AddText(Format("vL{}", A_Index), fake_label.Value) + try + local candidate := this["C" . A_Index] + catch + local candidate := this.AddText(Format("vC{}", A_Index), fake_candidate.Value) + try + local comment := this["M" . A_Index] + catch + local comment := this.AddText(Format("vM{}", A_Index), fake_comment.Value) label.Value := fake_label.Value fake_label.GetPos(&x, &y, &w, &h) label.Move(x, y, w, h) @@ -297,7 +302,6 @@ class CandidateBox { candidate.Visible := visible comment.Visible := visible } - this.num_candidates := max(this.num_candidates, num_candidates) fake_gui.GetPos(, , &width, &height) this.Move(, , width, height) From 8fb733c2104e54ce68892cc9b32884d11002b78f Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 9 Aug 2025 11:25:34 +0800 Subject: [PATCH 07/38] chore: adjust candidate box width --- Lib/RabbitCandidateBox.ahk | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 77a723a..191fc7b 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -180,6 +180,7 @@ class CandidateBox { local comment := this.AddText(Format("{} vM{}", position, A_Index), comment_text) comment.GetPos(, , &w, &h3) comment.Opt(Format("c{:x}", CandidateBox.comment_text_color)) + comment.Visible := this.has_comment this.max_comment_width := max(this.max_comment_width, w) this.candidate_height := max(this.candidate_height, h1, h2, h3) @@ -253,8 +254,8 @@ class CandidateBox { this.post.Visible := !!post ; reset candidates - hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) - normal_opt := Format("c{:x} Background{:x}", CandidateBox.text_color, CandidateBox.back_color) + hilited_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) + normal_opt := Format("c{:x} Background{:x} {}", CandidateBox.text_color, CandidateBox.back_color, CandidateBox.border) this.num_candidates := max(this.num_candidates, num_candidates) loop this.num_candidates { if A_Index > num_candidates { @@ -291,16 +292,16 @@ class CandidateBox { if A_Index == hilited_index { label.Opt(hilited_opt) candidate.Opt(hilited_opt) - comment.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color)) + comment.Opt(Format("c{:x} Background{:x} {}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border)) } else { label.Opt(normal_opt) candidate.Opt(normal_opt) - comment.Opt(Format("c{:x} Background{:x}", CandidateBox.comment_text_color, CandidateBox.back_color)) + comment.Opt(Format("c{:x} Background{:x} {}", CandidateBox.comment_text_color, CandidateBox.back_color, CandidateBox.border)) } local visible := (A_Index <= num_candidates) label.Visible := visible candidate.Visible := visible - comment.Visible := visible + comment.Visible := (fake_gui.has_comment && visible) } fake_gui.GetPos(, , &width, &height) From 56988ec2028b51d5b8c649a2aa85d45637b1257f Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 9 Aug 2025 18:30:05 +0800 Subject: [PATCH 08/38] fix: candidate width wrong --- Lib/RabbitCandidateBox.ahk | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 191fc7b..3efed18 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -196,6 +196,7 @@ class CandidateBox { local box_width := max(CandidateBox.min_width, list_width) if box_width > this.max_width && HasProp(this, "post") && this.post this.post.Move(, , this.post_width + box_width - this.max_width) + box_width := max(box_width, this.max_width) if box_width > list_width { this.max_candidate_width += box_width - list_width loop num_candidates From a99b5eba1b4149106780009cd0d6ad2fe6aed048 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 08:33:43 +0800 Subject: [PATCH 09/38] feat: support change labels chore: pass structs by reference --- Lib/RabbitCandidateBox.ahk | 55 +++++++++++++++++++------------------- Lib/librime-ahk | 2 +- Rabbit.ahk | 6 ++--- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 3efed18..752ae39 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -80,28 +80,11 @@ class CandidateBox { } } - Build(context, &width, &height) { - local cands := context.menu.candidates - GetCompositionText(context.composition, &pre_selected, &selected, &post_selected) - if !CandidateBox.gui || !CandidateBox.gui.built { - CandidateBox.gui := CandidateBox.BoxGui( - pre_selected, - selected, - post_selected, - cands, - context.menu.num_candidates, - context.menu.highlighted_candidate_index + 1 - ) - } else { - CandidateBox.gui.Update( - pre_selected, - selected, - post_selected, - cands, - context.menu.num_candidates, - context.menu.highlighted_candidate_index + 1 - ) - } + Build(&context, &width, &height) { + if !CandidateBox.gui || !CandidateBox.gui.built + CandidateBox.gui := CandidateBox.BoxGui(&context) + else + CandidateBox.gui.Update(&context) CandidateBox.gui.GetPos(, , &width, &height) } @@ -116,8 +99,16 @@ class CandidateBox { class BoxGui extends Gui { built := false - __New(pre, sel, post, cands, num_candidates, hilited_index) { + __New(&context, &pre?, &sel?, &post?, &menu?) { super.__New(, , this) + + menu := context.menu + local cands := menu.candidates + local num_candidates := menu.num_candidates + local hilited_index := menu.highlighted_candidate_index + 1 + local composition := context.composition + GetCompositionText(&composition, &pre, &sel, &post) + this.Opt(Format("-DPIScale -Caption +Owner +AlwaysOnTop {} {} {}", WS_EX_NOACTIVATE, WS_EX_COMPOSITED, WS_EX_LAYERED)) this.BackColor := CandidateBox.back_color this.SetFont(Format("s{} c{:x}", CandidateBox.font_point, CandidateBox.text_color), CandidateBox.font_face) @@ -164,9 +155,17 @@ class CandidateBox { this.max_comment_width := 0 this.candidate_height := 0 hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) + local has_label := !!context.select_labels[0] + local select_keys := menu.select_keys + local num_select_keys := StrLen(select_keys) loop num_candidates { position := Format("xs y+{} section {}", this.MarginY, CandidateBox.border) - local label := this.AddText(Format("Right {} vL{}", position, A_Index), A_Index . ". ") + local label_text := String(A_Index) + if A_Index <= menu.page_size && has_label + label_text := context.select_labels[A_Index] + else if A_Index <= num_select_keys + label_text := SubStr(select_keys, A_Index, 1) + local label := this.AddText(Format("Right {} vL{}", position, A_Index), label_text . " ") label.GetPos(, , &w, &h1) this.max_label_width := max(this.max_label_width, w + this.MarginX) @@ -222,8 +221,10 @@ class CandidateBox { this.built := true } - Update(pre, sel, post, cands, num_candidates, hilited_index) { - local fake_gui := CandidateBox.BoxGui(pre, sel, post, cands, num_candidates, hilited_index) + Update(&context) { + local fake_gui := CandidateBox.BoxGui(&context, &pre, &sel, &post, &menu) + local num_candidates := menu.num_candidates + local hilited_index := menu.highlighted_candidate_index + 1 ; reset preedit if pre { @@ -311,7 +312,7 @@ class CandidateBox { } } -GetCompositionText(composition, &pre_selected, &selected, &post_selected) { +GetCompositionText(&composition, &pre_selected, &selected, &post_selected) { pre_selected := "" selected := "" post_selected := "" diff --git a/Lib/librime-ahk b/Lib/librime-ahk index bd0f16f..501b506 160000 --- a/Lib/librime-ahk +++ b/Lib/librime-ahk @@ -1 +1 @@ -Subproject commit bd0f16ff98658a40bea219b5be24d92835689f06 +Subproject commit 501b50607c7d8223c216968c968b9dcbdf36af3b diff --git a/Rabbit.ahk b/Rabbit.ahk index 2e0c55e..9efd95f 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -385,12 +385,12 @@ ProcessKey(key, mask, this_hotkey) { info := MonitorManage.GetMonitorInfo(hMon) show_at_left_top := !!info if show_at_left_top && !last_is_hide { - box.Build(context, &box_width, &box_height) + box.Build(&context, &box_width, &box_height) box.Show(info.work.left + 4, info.work.top + 4) } } if !show_at_left_top && GetCaretPos(&caret_x, &caret_y, &caret_w, &caret_h) { - box.Build(context, &box_width, &box_height) + box.Build(&context, &box_width, &box_height) if RabbitConfig.fix_candidate_box && prev_show { new_x := prev_x new_y := prev_y @@ -424,7 +424,7 @@ ProcessKey(key, mask, this_hotkey) { CoordMode("Mouse", "Screen") MouseGetPos(&mouse_x, &mouse_y) CoordMode("Mouse", backup_mouse_ref) - box.Build(context, &box_width, &box_height) + box.Build(&context, &box_width, &box_height) box.Show(mouse_x, mouse_y) } prev_show := true From bd4353420536d68d49f107bfaa1a8f663cbf4d75 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 13:56:51 +0800 Subject: [PATCH 10/38] feat: support more styles --- Lib/RabbitCandidateBox.ahk | 104 ++++++++++++++++++++----------------- Lib/RabbitUIStyle.ahk | 79 +++++++++++++++++++++++----- schemas/rabbit.yaml | 14 +++-- 3 files changed, 131 insertions(+), 66 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 752ae39..b88e975 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -27,20 +27,7 @@ global WS_EX_LAYERED := "+E0x00080000" class CandidateBox { static dbg := false static gui := 0 - static back_color := 0xeeeeec - static text_color := 0x000000 - static font_face := "Microsoft YaHei UI" - static font_point := 12 - static comment_text_color := 0x222222 - static hilited_back_color := 0x000000 - static hilited_text_color := 0xffffff - static hilited_candidate_text_color := 0xffffff - static hilited_candidate_back_color := 0x000000 - static hilited_comment_text_color := 0x222222 - static margin_x := 5 - static margin_y := 5 - static min_width := 150 - static border := CandidateBox.dbg ? "+border" : 0 + static border := CandidateBox.dbg ? "+border" : 0 __New() { this.UpdateUIStyle() @@ -51,32 +38,43 @@ class CandidateBox { del_opaque(color) { return color & 0xffffff } - CandidateBox.back_color := del_opaque(UIStyle.back_color) CandidateBox.text_color := del_opaque(UIStyle.text_color) - if UIStyle.font_face - CandidateBox.font_face := UIStyle.font_face - CandidateBox.font_point := UIStyle.font_point + CandidateBox.back_color := del_opaque(UIStyle.back_color) + CandidateBox.candidate_text_color := del_opaque(UIStyle.candidate_text_color) + CandidateBox.candidate_back_color := del_opaque(UIStyle.candidate_back_color) + CandidateBox.label_color := del_opaque(UIStyle.label_color) CandidateBox.comment_text_color := del_opaque(UIStyle.comment_text_color) - CandidateBox.hilited_back_color := del_opaque(UIStyle.hilited_back_color) CandidateBox.hilited_text_color := del_opaque(UIStyle.hilited_text_color) - CandidateBox.hilited_candidate_back_color := del_opaque(UIStyle.hilited_candidate_back_color) + CandidateBox.hilited_back_color := del_opaque(UIStyle.hilited_back_color) CandidateBox.hilited_candidate_text_color := del_opaque(UIStyle.hilited_candidate_text_color) + CandidateBox.hilited_candidate_back_color := del_opaque(UIStyle.hilited_candidate_back_color) + CandidateBox.hilited_label_color := del_opaque(UIStyle.hilited_label_color) CandidateBox.hilited_comment_text_color := del_opaque(UIStyle.hilited_comment_text_color) - CandidateBox.margin_x := UIStyle.margin_x - CandidateBox.margin_y := UIStyle.margin_y + + CandidateBox.base_opt := Format("c{:x} Background{:x} {}", CandidateBox.text_color, CandidateBox.back_color, CandidateBox.border) + CandidateBox.candidate_opt := Format("c{:x} Background{:x} {}", CandidateBox.candidate_text_color, CandidateBox.candidate_back_color, CandidateBox.border) + CandidateBox.label_opt := Format("c{:x} Background{:x} {}", CandidateBox.label_color, CandidateBox.candidate_back_color, CandidateBox.border) + CandidateBox.comment_opt := Format("c{:x} Background{:x} {}", CandidateBox.comment_text_color, CandidateBox.candidate_back_color, CandidateBox.border) + CandidateBox.hilited_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color, CandidateBox.border) + CandidateBox.hilited_candidate_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) + CandidateBox.hilited_label_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_label_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) + CandidateBox.hilited_comment_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) + + CandidateBox.base_font_opt := Format("s{} q5", UIStyle.font_point) + CandidateBox.label_font_opt := Format("s{} q5", UIStyle.label_font_point) + CandidateBox.comment_font_opt := Format("s{} q5", UIStyle.comment_font_point) if CandidateBox.gui { CandidateBox.gui.BackColor := CandidateBox.back_color - CandidateBox.gui.SetFont(Format("s{} c{:x}", CandidateBox.font_point, CandidateBox.text_color), CandidateBox.font_face) - CandidateBox.gui.MarginX := CandidateBox.margin_x - CandidateBox.gui.MarginY := CandidateBox.margin_y + CandidateBox.gui.MarginX := UIStyle.margin_x + CandidateBox.gui.MarginY := UIStyle.margin_y if HasProp(CandidateBox.gui, "pre") && CandidateBox.gui.pre - CandidateBox.gui.pre.Opt(Format("c{:x}", CandidateBox.text_color)) + CandidateBox.gui.pre.Opt(CandidateBox.base_opt) if HasProp(CandidateBox.gui, "sel") && CandidateBox.gui.sel - CandidateBox.gui.sel.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color)) + CandidateBox.gui.sel.Opt(CandidateBox.hilited_opt) if HasProp(CandidateBox.gui, "post") && CandidateBox.gui.post - CandidateBox.gui.post.Opt(Format("c{:x}", CandidateBox.text_color)) + CandidateBox.gui.post.Opt(CandidateBox.base_opt) } } @@ -111,14 +109,12 @@ class CandidateBox { this.Opt(Format("-DPIScale -Caption +Owner +AlwaysOnTop {} {} {}", WS_EX_NOACTIVATE, WS_EX_COMPOSITED, WS_EX_LAYERED)) this.BackColor := CandidateBox.back_color - this.SetFont(Format("s{} c{:x}", CandidateBox.font_point, CandidateBox.text_color), CandidateBox.font_face) - this.MarginX := CandidateBox.margin_x - this.MarginY := CandidateBox.margin_y + this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + this.MarginX := UIStyle.margin_x + this.MarginY := UIStyle.margin_y this.num_candidates := num_candidates this.has_comment := false - local hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color) - ; build preedit this.max_width := 0 this.preedit_height := 0 @@ -126,6 +122,7 @@ class CandidateBox { local position := head_position if pre { this.pre := this.AddText(position, pre) + this.pre.Opt(CandidateBox.base_opt) position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) this.pre.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) @@ -134,7 +131,7 @@ class CandidateBox { } if sel { this.sel := this.AddText(position, sel) - this.sel.Opt(hilited_opt) + this.sel.Opt(CandidateBox.hilited_opt) position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) this.sel.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) @@ -143,6 +140,7 @@ class CandidateBox { } if post { this.post := this.AddText(position, post) + this.post.Opt(CandidateBox.base_opt) this.post.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) this.post_width := w @@ -154,7 +152,6 @@ class CandidateBox { this.max_candidate_width := 0 this.max_comment_width := 0 this.candidate_height := 0 - hilited_opt := Format("c{:x} Background{:x}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color) local has_label := !!context.select_labels[0] local select_keys := menu.select_keys local num_select_keys := StrLen(select_keys) @@ -165,17 +162,21 @@ class CandidateBox { label_text := context.select_labels[A_Index] else if A_Index <= num_select_keys label_text := SubStr(select_keys, A_Index, 1) - local label := this.AddText(Format("Right {} vL{}", position, A_Index), label_text . " ") + label_text := Format(UIStyle.label_format, label_text) + this.SetFont(CandidateBox.label_font_opt, UIStyle.label_font_face) + local label := this.AddText(Format("Right {} vL{}", position, A_Index), label_text) label.GetPos(, , &w, &h1) this.max_label_width := max(this.max_label_width, w + this.MarginX) position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) local candidate := this.AddText(Format("{} vC{}", position, A_Index), cands[A_Index].text) candidate.GetPos(, , &w, &h2) this.max_candidate_width := max(this.max_candidate_width, w + this.MarginX) if comment_text := cands[A_Index].comment this.has_comment := true + this.SetFont(CandidateBox.comment_font_opt, UIStyle.comment_font_face) local comment := this.AddText(Format("{} vM{}", position, A_Index), comment_text) comment.GetPos(, , &w, &h3) comment.Opt(Format("c{:x}", CandidateBox.comment_text_color)) @@ -184,15 +185,19 @@ class CandidateBox { this.candidate_height := max(this.candidate_height, h1, h2, h3) if A_Index == hilited_index { - label.Opt(hilited_opt) - candidate.Opt(hilited_opt) - comment.Opt(Format("c{:x} Background{:x}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color)) + label.Opt(CandidateBox.hilited_label_opt) + candidate.Opt(CandidateBox.hilited_candidate_opt) + comment.Opt(CandidateBox.hilited_comment_opt) + } else { + label.Opt(CandidateBox.label_opt) + candidate.Opt(CandidateBox.candidate_opt) + comment.Opt(CandidateBox.comment_opt) } } ; adjust width height local list_width := this.max_label_width + this.max_candidate_width + this.has_comment * this.max_comment_width - local box_width := max(CandidateBox.min_width, list_width) + local box_width := max(UIStyle.min_width, list_width) if box_width > this.max_width && HasProp(this, "post") && this.post this.post.Move(, , this.post_width + box_width - this.max_width) box_width := max(box_width, this.max_width) @@ -225,6 +230,7 @@ class CandidateBox { local fake_gui := CandidateBox.BoxGui(&context, &pre, &sel, &post, &menu) local num_candidates := menu.num_candidates local hilited_index := menu.highlighted_candidate_index + 1 + this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) ; reset preedit if pre { @@ -256,9 +262,6 @@ class CandidateBox { this.post.Visible := !!post ; reset candidates - hilited_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) - normal_opt := Format("c{:x} Background{:x} {}", CandidateBox.text_color, CandidateBox.back_color, CandidateBox.border) - this.num_candidates := max(this.num_candidates, num_candidates) loop this.num_candidates { if A_Index > num_candidates { this["L" . A_Index].Visible := false @@ -269,14 +272,17 @@ class CandidateBox { local fake_label := fake_gui["L" . A_Index] local fake_candidate := fake_gui["C" . A_Index] local fake_comment := fake_gui["M" . A_Index] + this.SetFont(CandidateBox.label_font_opt, UIStyle.label_font_face) try local label := this["L" . A_Index] catch local label := this.AddText(Format("vL{}", A_Index), fake_label.Value) + this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) try local candidate := this["C" . A_Index] catch local candidate := this.AddText(Format("vC{}", A_Index), fake_candidate.Value) + this.SetFont(CandidateBox.comment_font_opt, UIStyle.comment_font_face) try local comment := this["M" . A_Index] catch @@ -292,13 +298,13 @@ class CandidateBox { comment.Move(x, y, w, h) if A_Index == hilited_index { - label.Opt(hilited_opt) - candidate.Opt(hilited_opt) - comment.Opt(Format("c{:x} Background{:x} {}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border)) + label.Opt(CandidateBox.hilited_label_opt) + candidate.Opt(CandidateBox.hilited_candidate_opt) + comment.Opt(CandidateBox.hilited_comment_opt) } else { - label.Opt(normal_opt) - candidate.Opt(normal_opt) - comment.Opt(Format("c{:x} Background{:x} {}", CandidateBox.comment_text_color, CandidateBox.back_color, CandidateBox.border)) + label.Opt(CandidateBox.label_opt) + candidate.Opt(CandidateBox.candidate_opt) + comment.Opt(CandidateBox.comment_opt) } local visible := (A_Index <= num_candidates) label.Visible := visible diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index 6b60309..48cc38e 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -20,19 +20,30 @@ class UIStyle { static use_dark := false - static text_color := 0xff000000 - static back_color := 0xffeceeee static font_face := "Microsoft YaHei UI" - static font_point := 12 - static comment_text_color := 0xff222222 - static hilited_back_color := 0xff000000 - static hilited_text_color := 0xffffffff - static hilited_candidate_text_color := 0xffffffff - static hilited_candidate_back_color := 0xff000000 - static hilited_comment_text_color := 0xff222222 + static label_font_face := "Microsoft YaHei UI" + static comment_font_face := "Microsoft YaHei UI" + static font_point := 14 + static label_font_point := 14 + static comment_font_point := 14 + static label_format := "{}. " + static margin_x := 5 static margin_y := 5 - static min_width := 150 + static min_width := 160 + + static text_color := 0xff000000 + static back_color := 0xffeeeeec + static candidate_text_color := 0xff000000 + static candidate_back_color := 0xffeeeeec + static label_color := 0xff000000 + static comment_text_color := 0xff000000 + static hilited_text_color := 0xff000000 + static hilited_back_color := 0xffd4d4d4 + static hilited_candidate_text_color := 0xffffffff + static hilited_candidate_back_color := 0xff0a3afa + static hilited_label_color := 0xffffffff + static hilited_comment_text_color := 0xff000000 static Update(config, initialize) { global rime @@ -42,9 +53,23 @@ class UIStyle { UIStyle.font_face := rime.config_get_string(config, "style/font_face") if not UIStyle.font_face UIStyle.font_face := "Microsoft YaHei UI" + UIStyle.label_font_face := rime.config_get_string(config, "style/label_font_face") + if not UIStyle.label_font_face + UIStyle.label_font_face := "Microsoft YaHei UI" + UIStyle.comment_font_face := rime.config_get_string(config, "style/comment_font_face") + if not UIStyle.comment_font_face + UIStyle.comment_font_face := "Microsoft YaHei UI" UIStyle.font_point := rime.config_get_int(config, "style/font_point") if UIStyle.font_point <= 0 - UIStyle.font_point := 12 + UIStyle.font_point := 14 + UIStyle.label_font_point := rime.config_get_int(config, "style/label_font_point") + if UIStyle.label_font_point <= 0 + UIStyle.label_font_point := 14 + UIStyle.comment_font_point := rime.config_get_int(config, "style/comment_font_point") + if UIStyle.comment_font_point <= 0 + UIStyle.comment_font_point := 14 + if rime.config_test_get_string(config, "style/label_format", &fmt) && fmt + UIStyle.label_format := fmt if rime.config_test_get_int(config, "style/layout/margin_x", &mx) && mx >= 0 UIStyle.margin_x := mx if rime.config_test_get_int(config, "style/layout/margin_y", &my) && my >= 0 @@ -67,18 +92,46 @@ class UIStyle { UIStyle.text_color := UIStyle.GetColor(config, prefix . "/text_color", fmt, 0xff000000) UIStyle.back_color := UIStyle.GetColor(config, prefix . "/back_color", fmt, 0xffeceeee) - UIStyle.comment_text_color := UIStyle.GetColor(config, prefix . "/comment_text_color", fmt, UIStyle.text_color) + UIStyle.candidate_text_color := UIStyle.GetColor(config, prefix . "/candidate_text_color", fmt, UIStyle.text_color) + UIStyle.candidate_back_color := UIStyle.GetColor(config, prefix . "/candidate_back_color", fmt, UIStyle.back_color) + UIStyle.label_color := UIStyle.GetColor(config, prefix . "/label_color", fmt, UIStyle.BlendColors(UIStyle.candidate_text_color, UIStyle.candidate_back_color)) + UIStyle.comment_text_color := UIStyle.GetColor(config, prefix . "/comment_text_color", fmt, UIStyle.label_color) UIStyle.hilited_text_color := UIStyle.GetColor(config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) UIStyle.hilited_back_color := UIStyle.GetColor(config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) UIStyle.hilited_candidate_text_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) UIStyle.hilited_candidate_back_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) - UIStyle.hilited_comment_text_color := UIStyle.GetColor(config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_candidate_text_color) + UIStyle.hilited_label_color := UIStyle.GetColor(config, prefix . "/hilited_label_color", fmt, UIStyle.BlendColors(UIStyle.hilited_candidate_text_color, UIStyle.hilited_candidate_back_color)) + UIStyle.hilited_comment_text_color := UIStyle.GetColor(config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_label_color) return true } return false } + static BlendColors(fcolor, bcolor) { + local fA := (fcolor >> 24) & 0xff + if fA == 0xff + return fcolor + local fR := (fcolor >> 16) & 0xff + local fG := (fcolor >> 8) & 0xff + local fB := fcolor & 0xff + local bA := (bcolor >> 24) & 0xff + local bR := (bcolor >> 16) & 0xff + local bG := (bcolor >> 8) & 0xff + local bB := bcolor & 0xff + + local fAlpha := fA / 255.0 + local bAlpha := bA / 255.0 + + local retAlpha := fAlpha + bAlpha * (1 - fAlpha) + + local retR := Integer((fR * fAlpha + bR * bAlpha * (1 - fAlpha)) / retAlpha) + local retG := Integer((fG * fAlpha + bG * bAlpha * (1 - fAlpha)) / retAlpha) + local retB := Integer((fB * fAlpha + bB * bAlpha * (1 - fAlpha)) / retAlpha) + + return (Integer(retAlpha) * 255 << 24) | (retR << 16) | (retG << 8) | retB + } + static GetColor(config, key, fmt, fallback) { global rime if not rime.config_test_get_string(config, key, &color) diff --git a/schemas/rabbit.yaml b/schemas/rabbit.yaml index 9fea6fa..dd506db 100644 --- a/schemas/rabbit.yaml +++ b/schemas/rabbit.yaml @@ -26,12 +26,18 @@ fix_candidate_box: false style: color_scheme: aqua - font_point: 12 + font_face: Microsoft YaHei UI + label_font_face: Microsoft YaHei UI + comment_font_face: Microsoft YaHei UI + font_point: 14 + label_font_point: 14 + comment_font_point: 14 + # 不同于小狼毫,格式化标签文本需符合 AutoHotkey 语法 + # 详见 https://wyagd001.github.io/v2/docs/lib/Format.htm#FormatSpec + label_format: "{:s}. " # Copied from weasel.yaml -# Current supported fields -# - text_color -# - back_color +# 默认颜色格式为 argb,暂不支持 alpha 通道 preset_color_schemes: aqua: name: 碧水/Aqua From e3aedc46ed22690cb81c75e10c9ed7b4a7381720 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 14:34:44 +0800 Subject: [PATCH 11/38] fix: candidates not shown --- Lib/RabbitCandidateBox.ahk | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index b88e975..eacafd2 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -231,6 +231,7 @@ class CandidateBox { local num_candidates := menu.num_candidates local hilited_index := menu.highlighted_candidate_index + 1 this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + this.num_candidates := max(this.num_candidates, num_candidates) ; reset preedit if pre { From 5ef7abb8204682e74d52e25feaac0598da920754 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 14:36:42 +0800 Subject: [PATCH 12/38] chore: pass config by reference --- Lib/RabbitConfig.ahk | 4 ++-- Lib/RabbitUIStyle.ahk | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/RabbitConfig.ahk b/Lib/RabbitConfig.ahk index 056d96a..a73dadf 100644 --- a/Lib/RabbitConfig.ahk +++ b/Lib/RabbitConfig.ahk @@ -59,10 +59,10 @@ class RabbitConfig { if rime.config_test_get_bool(config, "fix_candidate_box", &result) RabbitConfig.fix_candidate_box := !!result - UIStyle.Update(config, true) + UIStyle.Update(&config, true) if IS_DARK_MODE := RabbitIsUserDarkMode() { if color_name := rime.config_get_string(config, "style/color_scheme_dark") - UIStyle.use_dark := UIStyle.UpdateColor(config, color_name) + UIStyle.use_dark := UIStyle.UpdateColor(&config, color_name) DarkMode.set(IS_DARK_MODE) } diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index 48cc38e..5cb06e5 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -45,7 +45,7 @@ class UIStyle { static hilited_label_color := 0xffffffff static hilited_comment_text_color := 0xff000000 - static Update(config, initialize) { + static Update(&config, initialize) { global rime if !rime || !config return @@ -77,10 +77,10 @@ class UIStyle { if rime.config_test_get_int(config, "style/layout/min_width", &w) && w >= 0 UIStyle.min_width := w if initialize and color := rime.config_get_string(config, "style/color_scheme") - UIStyle.UpdateColor(config, color) + UIStyle.UpdateColor(&config, color) } - static UpdateColor(config, color) { + static UpdateColor(&config, color) { global rime if color or (buffer := rime.config_get_string(config, "style/color_scheme")) { local prefix := "preset_color_schemes/" . (color ? color : buffer) @@ -90,18 +90,18 @@ class UIStyle { fmt := cfmt } - UIStyle.text_color := UIStyle.GetColor(config, prefix . "/text_color", fmt, 0xff000000) - UIStyle.back_color := UIStyle.GetColor(config, prefix . "/back_color", fmt, 0xffeceeee) - UIStyle.candidate_text_color := UIStyle.GetColor(config, prefix . "/candidate_text_color", fmt, UIStyle.text_color) - UIStyle.candidate_back_color := UIStyle.GetColor(config, prefix . "/candidate_back_color", fmt, UIStyle.back_color) - UIStyle.label_color := UIStyle.GetColor(config, prefix . "/label_color", fmt, UIStyle.BlendColors(UIStyle.candidate_text_color, UIStyle.candidate_back_color)) - UIStyle.comment_text_color := UIStyle.GetColor(config, prefix . "/comment_text_color", fmt, UIStyle.label_color) - UIStyle.hilited_text_color := UIStyle.GetColor(config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) - UIStyle.hilited_back_color := UIStyle.GetColor(config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) - UIStyle.hilited_candidate_text_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) - UIStyle.hilited_candidate_back_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) - UIStyle.hilited_label_color := UIStyle.GetColor(config, prefix . "/hilited_label_color", fmt, UIStyle.BlendColors(UIStyle.hilited_candidate_text_color, UIStyle.hilited_candidate_back_color)) - UIStyle.hilited_comment_text_color := UIStyle.GetColor(config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_label_color) + UIStyle.text_color := UIStyle.GetColor(&config, prefix . "/text_color", fmt, 0xff000000) + UIStyle.back_color := UIStyle.GetColor(&config, prefix . "/back_color", fmt, 0xffeceeee) + UIStyle.candidate_text_color := UIStyle.GetColor(&config, prefix . "/candidate_text_color", fmt, UIStyle.text_color) + UIStyle.candidate_back_color := UIStyle.GetColor(&config, prefix . "/candidate_back_color", fmt, UIStyle.back_color) + UIStyle.label_color := UIStyle.GetColor(&config, prefix . "/label_color", fmt, UIStyle.BlendColors(UIStyle.candidate_text_color, UIStyle.candidate_back_color)) + UIStyle.comment_text_color := UIStyle.GetColor(&config, prefix . "/comment_text_color", fmt, UIStyle.label_color) + UIStyle.hilited_text_color := UIStyle.GetColor(&config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) + UIStyle.hilited_back_color := UIStyle.GetColor(&config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) + UIStyle.hilited_candidate_text_color := UIStyle.GetColor(&config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) + UIStyle.hilited_candidate_back_color := UIStyle.GetColor(&config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) + UIStyle.hilited_label_color := UIStyle.GetColor(&config, prefix . "/hilited_label_color", fmt, UIStyle.BlendColors(UIStyle.hilited_candidate_text_color, UIStyle.hilited_candidate_back_color)) + UIStyle.hilited_comment_text_color := UIStyle.GetColor(&config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_label_color) return true } @@ -132,7 +132,7 @@ class UIStyle { return (Integer(retAlpha) * 255 << 24) | (retR << 16) | (retG << 8) | retB } - static GetColor(config, key, fmt, fallback) { + static GetColor(&config, key, fmt, fallback) { global rime if not rime.config_test_get_string(config, key, &color) return fallback From 3805890de1c4f88803b9f09b20250e95acab40ea Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 14:53:15 +0800 Subject: [PATCH 13/38] fix: config not pass by ref on color change --- Lib/RabbitUIStyle.ahk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index 5cb06e5..f47121c 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -208,10 +208,10 @@ OnColorChange(wParam, lParam, msg, hWnd) { IS_DARK_MODE := RabbitIsUserDarkMode() if old_dark != IS_DARK_MODE { if config := rime.config_open("rabbit") { - UIStyle.Update(config, true) + UIStyle.Update(&config, true) if IS_DARK_MODE { if color_name := rime.config_get_string(config, "style/color_scheme_dark") - UIStyle.use_dark := UIStyle.UpdateColor(config, color_name) + UIStyle.use_dark := UIStyle.UpdateColor(&config, color_name) } rime.config_close(config) From 494dd3e8ff2239c1e5fecf8c3d5f5ebf95f8a09e Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 10 Aug 2025 16:43:25 +0800 Subject: [PATCH 14/38] fix: not get correct width height --- Lib/RabbitCandidateBox.ahk | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index eacafd2..7cb6c99 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -83,7 +83,8 @@ class CandidateBox { CandidateBox.gui := CandidateBox.BoxGui(&context) else CandidateBox.gui.Update(&context) - CandidateBox.gui.GetPos(, , &width, &height) + width := CandidateBox.gui.max_width + height := CandidateBox.gui.max_height } Show(x, y) { @@ -200,9 +201,9 @@ class CandidateBox { local box_width := max(UIStyle.min_width, list_width) if box_width > this.max_width && HasProp(this, "post") && this.post this.post.Move(, , this.post_width + box_width - this.max_width) - box_width := max(box_width, this.max_width) - if box_width > list_width { - this.max_candidate_width += box_width - list_width + this.max_width := max(box_width, this.max_width) + if this.max_width > list_width { + this.max_candidate_width += this.max_width - list_width loop num_candidates this["C" . A_Index].Move(, , this.max_candidate_width) } @@ -222,6 +223,8 @@ class CandidateBox { max_h := max(max_h, h) y += (max_h + this.MarginY) } + this.max_height := y + this.max_width += (2 * this.MarginX) this.built := true } @@ -232,6 +235,8 @@ class CandidateBox { local hilited_index := menu.highlighted_candidate_index + 1 this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) this.num_candidates := max(this.num_candidates, num_candidates) + this.max_width := fake_gui.max_width + this.max_height := fake_gui.max_height ; reset preedit if pre { From e2fe32c2049d5809e37c816151e3c880c7daf950 Mon Sep 17 00:00:00 2001 From: zerxmega Date: Sun, 10 Aug 2025 20:36:17 +0800 Subject: [PATCH 15/38] add theme choice gui --- .gitignore | 1 + Lib/RabbitThemesUI.ahk | 223 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 Lib/RabbitThemesUI.ahk diff --git a/.gitignore b/.gitignore index 4ba4a2d..b58767d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ Rime Data +dist test.ahk *.exe *.dll diff --git a/Lib/RabbitThemesUI.ahk b/Lib/RabbitThemesUI.ahk new file mode 100644 index 0000000..b3037bb --- /dev/null +++ b/Lib/RabbitThemesUI.ahk @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2005 Tim + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#Include + +class ThemesGUI { + __New(color_schemes) { + this.preset_color_schemes := color_schemes + this.colorSchemeMap := Map() + this.previewFontName := "Microsoft YaHei UI" + this.previewFontSize := 12 + this.themeListBoxW := 400 + this.previewGroupW := 300 + this.previewGroupH := 418 + this.currentTheme := "aqua" + this.gui := Gui("+LastFound +OwnDialogs -DPIScale +AlwaysOnTop", "选择主题") + this.gui.SetFont("s10", "Microsoft YaHei UI") + this.Build() + } + + Show() { + this.gui.Show("AutoSize") + } + + Build() { + this.gui.Add("Text", "x10 y10", "主题:") + colorChoices := [] + for key, preset in this.preset_color_schemes { + colorChoices.Push(preset["name"]) + this.colorSchemeMap[preset["name"]] := key + } + themeListBox := this.gui.AddListBox("r15 w" . this.themeListBoxW . " -Multi", colorChoices) + themeListBox.Choose(1) + themeListBox.OnEvent("Change", this.OnChangeColorScheme.Bind(this)) + this.gui.AddGroupBox("x+20 yp-8 w" . this.previewGroupW . " h" . this.previewGroupH . " Section", "预览") + + this.currentTheme := this.colorSchemeMap[themeListBox.Text] + colorOpt := this.GetThemeColor(this.currentTheme) + highlightColorOpt := this.GetThemeColor(this.currentTheme, isHighlight := true) + + this.preeditTxt := " [shu ru fa]" ; ‸ + this.selCandsTxt := " 1. 输入法" + this.candidateTxt := " 2. 输入`n 3. 数`n 4. 书`n 5. 输" + p := this.GetPreviewCandsBoxRect() + pX := p[1], pY := p[2], pW := p[3], pRowH := p[4] + + this.previewGuiPreedit := this.gui.AddText( + Format("xp+{:d} yp+{:d} w{:d} h{:d} {:s}", pX, pY, pW, pRowH, colorOpt), + this.preeditTxt + ) + this.previewGuiSelCand := this.gui.AddText( + Format("xp yp+{:d} w{:d} h{:d} {:s}", pRowH, pW, pRowH, highlightColorOpt), + this.selCandsTxt + ) + this.previewGuiCands := this.gui.AddText( + Format("xp yp+{:d} w{:d} h{:d} {:s}", pRowH, pW, pRowH * 4, colorOpt), + this.candidateTxt + ) + this.SetPreviewCandsBoxFont() + + this.setFontBtn := this.gui.Add("Button", "x10 ys+440 w160", "设置字体") + this.confirmBtn := this.gui.Add("Button", "x+400 ys+440 w160", "确定") + this.setFontBtn.OnEvent("Click", this.OnSetFont.Bind(this)) + this.confirmBtn.OnEvent("Click", this.OnConfirm.Bind(this)) + } + + OnChangeColorScheme(ctrl, info) { + if !this.colorSchemeMap.Has(ctrl.Text) + return + + this.currentTheme := this.colorSchemeMap[ctrl.Text] + this.previewGuiPreedit.Opt(this.GetThemeColor(this.currentTheme)) + this.previewGuiSelCand.Opt(this.GetThemeColor(this.currentTheme, darken := true)) + this.previewGuiCands.Opt(this.GetThemeColor(this.currentTheme)) + } + + OnSetFont(*) { + fontGui := Gui("AlwaysOnTop +Owner" this.gui.Hwnd, "字体选择") + fontGui.SetFont("s10") + + fontGui.Add("Text", "x10 y10", "字体名称:") + fontChoice := fontGui.AddDropDownList("x+10 yp-4 w180 hp r10", GUIUtilities.GetFontArray()) + fontChoice.Text := this.previewFontName + + fontGui.Add("Text", "x+30 y10", "大小:") + fontSizeEdit := fontGui.Add("Edit", "x+0 yp-6 w60 Limit2 Number") + fontGui.Add("UpDown", "Range8-20", this.previewFontSize) + + okBtn := fontGui.Add("Button", "x10 yp+30 w120", "确定") + fontGui.Add("Button", "x+150 yp w120", "取消").OnEvent("Click", (*) => fontGui.Destroy()) + + okBtn.OnEvent("Click", (*) => ( + this.previewFontName := fontChoice.Text, + this.previewFontSize := fontSizeEdit.Value, + this.SetPreviewCandsBoxFont(), + this.SetPreviewCandsBoxSize(), + fontGui.Destroy() + )) + + fontGui.Show() + } + + OnConfirm(*) { + global rime + if rime and config := rime.config_open("rabbit") { + rime.config_set_string(config, "style/color_scheme", this.currentTheme) + rime.config_set_int(config, "style/font_point", this.previewFontSize) + rime.config_set_string(config, "style/font_face", this.previewFontName) + UIStyle.Update(config, true) + rime.config_close(config) + box.UpdateUIStyle() + } + + this.gui.Hide() + } + + GetPreviewCandsBoxRect(refClient := false) { + previewTxtDim := GUIUtilities.GetTextDim(this.selCandsTxt, this.previewFontName, this.previewFontSize) + previewCandsBoxW := previewTxtDim[1] + 30 + previewCandsBoxRowH := previewTxtDim[2] + 4 + previewCandsBoxX := Round((this.previewGroupW - previewCandsBoxW) / 2) + previewCandsBoxY := Round((this.previewGroupH - previewCandsBoxRowH * 6) / 2) + if refClient { + previewCandsBoxX := previewCandsBoxX + this.themeListBoxW + 30 + previewCandsBoxY := previewCandsBoxY + 40 + } + return [previewCandsBoxX, previewCandsBoxY, previewCandsBoxW, previewCandsBoxRowH] + } + + SetPreviewCandsBoxFont() { + fontSizeOpt := "s" . this.previewFontSize + this.previewGuiPreedit.SetFont(fontSizeOpt, this.previewFontName) + this.previewGuiSelCand.SetFont(fontSizeOpt, this.previewFontName) + this.previewGuiCands.SetFont(fontSizeOpt, this.previewFontName) + } + + SetPreviewCandsBoxSize() { + p := this.GetPreviewCandsBoxRect(refClient := true) + newX := p[1], newY := p[2], newW := p[3], newRowH := p[4] + this.previewGuiPreedit.Move(newX, newY, newW, newRowH) + this.previewGuiSelCand.Move(newX, newY + newRowH, newW, newRowH) + this.previewGuiCands.Move(newX, newY + newRowH * 2, newW, newRowH * 4) + } + + GetThemeColor(selTheme, isHighlight := false) { + style := this.preset_color_schemes[selTheme] + text_color := UIStyle.ParseColor(style["text_color"], "abgr", 0xff000000) & 0xffffff + back_color := UIStyle.ParseColor(style["back_color"], "abgr", 0xffeceeee) & 0xffffff + if isHighlight + back_color := DarkenColor(back_color, 15) + + return Format("c{:x} Background{:x}", text_color, back_color) + + DarkenColor(color, percent) { + factor := 1 - (percent / 100) + + r := (color >> 16) & 0xFF + g := (color >> 8) & 0xFF + b := color & 0xFF + + r := Min(Max(Round(r * factor), 0), 255) + g := Min(Max(Round(g * factor), 0), 255) + b := Min(Max(Round(b * factor), 0), 255) + + return (r << 16) | (g << 8) | b + } + } +} + +Class GUIUtilities { + static GetTextDim(text, fontName, fontSize) { + hDC := DllCall("GetDC", "UPtr", 0) + ; fontHeight: Round(fontSize * A_ScreenDPI / 72) + nHeight := -DllCall("MulDiv", "Int", fontSize, "Int", DllCall("GetDeviceCaps", "UPtr", hDC, "Int", 90), "Int", 72) + ; fontWeight: regular -> 400 + hFont := DllCall("CreateFont", "Int", nHeight, "Int", 0, "Int", 0, "Int", 0, "Int", fontWeight := 400, "UInt", false, "UInt", false, "UInt", false, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0, "WStr", fontName) + DllCall("SelectObject", "UPtr", hDC, "UPtr", hFont, "UPtr") + DllCall("GetTextExtentPoint32", "ptr", hDC, "WStr", text, "Int", StrLen(text), "int64*", &nSize := 0) + + DllCall("DeleteObject", "Uint", hFont) + DllCall("ReleaseDC", "Uint", 0, "Uint", hDC) + + nWidth := nSize & 0xffffffff + nHeight := nSize >> 32 + return [nWidth, nHeight] + } + + static GetFontArray() { + static fontArr + if isSet(fontArr) + return fontArr + + sFont := Buffer(128, 0) + NumPut("UChar", 1, sFont, 23) + DllCall("EnumFontFamiliesEx", "ptr", DllCall("GetDC", "ptr", 0), "ptr", sFont.Ptr, "ptr", CallbackCreate(EnumFontProc), "ptr", ObjPtr(fontMap := Map()), "uint", 0) + + fontArr := Array() + for key, value in fontMap + fontArr.Push(SubStr(key, 2)) ; remove "@" + return fontArr + + EnumFontProc(lpFont, lpntme, textFont, lParam) { + font := StrGet(lpFont + 28, "UTF-16") + ObjFromPtrAddRef(lParam)[font] := "" + return true + } + } +} From 7710a577b8ee6bf04b8935cdfbc74b2ddec1db4c Mon Sep 17 00:00:00 2001 From: Tim <67952813+rawbx@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:00:47 +0800 Subject: [PATCH 16/38] refactor themesUI using GDI+ to preivew (#18) * refactor themesUI using GDI+ to preivew * fix candidate preview position --- Lib/RabbitThemesUI.ahk | 421 ++++++++++++++++++++++++++++++----------- 1 file changed, 308 insertions(+), 113 deletions(-) diff --git a/Lib/RabbitThemesUI.ahk b/Lib/RabbitThemesUI.ahk index b3037bb..9b796b3 100644 --- a/Lib/RabbitThemesUI.ahk +++ b/Lib/RabbitThemesUI.ahk @@ -17,98 +17,289 @@ */ #Include +#Include + +class CandidatePreview { + borderWidth := 2 + cornerRadius := 4 + lineSpacing := 6 + padding := 8 + + pToken := 0 + pBitmap := 0 + hBitmap := 0 + pGraphics := 0 + hFont := 0 + hFormat := 0 + + __New(ctrl, theme, &calcW, &calcH) { + if !this.pToken { + this.pToken := Gdip_Startup() + if !this.pToken { + MsgBox("GDI+ failed to start.") + ExitApp + } + } + this.imgCtrl := ctrl + this.dpiSacle := GUIUtilities.GetMonitorDpiScale() + ; only use one font to preview + this.fontName := theme.HasOwnProp("font_face") ? theme.font_face : UIStyle.font_face + this.fontSize := theme.HasOwnProp("font_point") ? theme.font_point : UIStyle.font_point + ; preedite style + this.borderColor := theme.border_color + this.textColor := theme.text_color + this.backgroundColor := theme.back_color + this.hlTxtColor := theme.hilited_text_color + this.hlBgColor := theme.hilited_back_color + ; candidate style + this.hlCandTxtColor := theme.hilited_candidate_text_color + this.hlCandBgColor := theme.hilited_candidate_back_color + this.candTxtColor := theme.candidate_text_color + this.candBgColor := theme.candidate_back_color + this.CalcSize(&calcW, &calcH) + } + + __Delete() { + this.ReleaseAll() + } + + CalcSize(&calcW, &calcH) { + this.hFamily := Gdip_FontFamilyCreate(this.fontName) + this.hFont := Gdip_FontCreate(this.hFamily, this.fontSize * this.dpiSacle, regular := 0) + this.hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) + Gdip_SetStringFormatAlign(this.hFormat, left := 0) ; left:0, center:1, right:2 + + hDC := GetDC(this.imgCtrl.Hwnd) + pGraphics := Gdip_GraphicsFromHDC(hDC) + + CreateRectF(&RC, 0, 0, 0, 0) + this.prdSelSize := this.MeasureString(pGraphics, "RIME", this.hFont, this.hFormat, &RC) + this.prdHlSize := this.MeasureString(pGraphics, "shu ru fa", this.hFont, this.hFormat, &RC) + this.candSize := this.MeasureString(pGraphics, "1. 输入法", this.hFont, this.hFormat, &RC) + + this.maxRowWidth := this.prdSelSize.w + this.padding + this.prdHlSize.w + totalHeight := (this.prdSelSize.h + this.lineSpacing) * 6 + + Gdip_DeleteGraphics(pGraphics) + ReleaseDC(hDC, this.imgCtrl.Hwnd) + + this.previewWidth := Ceil(this.maxRowWidth) + this.padding * 2 + this.borderWidth * 2 + this.previewHeight := Ceil(totalHeight) + this.padding * 2 + this.borderWidth * 2 - this.lineSpacing ; Remove last line spacing + calcW := this.previewWidth + calcH := this.previewHeight + } + + Render(candsArray, selIndex) { + ; Create a bitmap in memory that matches the size of preview + this.pBitmap := Gdip_CreateBitmap(this.previewWidth, this.previewHeight) + this.pGraphics := Gdip_GraphicsFromImage(this.pBitmap) + Gdip_SetSmoothingMode(this.pGraphics, AntiAlias := 4) + + ; Draw border + if (this.borderWidth > 0) { + pBrushBorder := Gdip_BrushCreateSolid(this.borderColor) + this.FillRoundedRect(this.pGraphics, pBrushBorder, 0, 0, this.previewWidth, this.previewHeight, this.cornerRadius) + Gdip_DeleteBrush(pBrushBorder) + } + + ; Draw background + pBrushBg := Gdip_BrushCreateSolid(this.backgroundColor) + bgX := this.borderWidth + bgY := this.borderWidth + bgW := this.previewWidth - this.borderWidth * 2 + bgH := this.previewHeight - this.borderWidth * 2 + bgCornerRadius := this.cornerRadius > this.borderWidth ? this.cornerRadius - this.borderWidth : 0 + this.FillRoundedRect(this.pGraphics, pBrushBg, bgX, bgY, bgW, bgH, bgCornerRadius) + Gdip_DeleteBrush(pBrushBg) + + ; Draw preedit + currentY := this.padding + this.borderWidth + prdSelTextRect := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } + prdHlTextRect := { x: this.padding * 2 + this.prdSelSize.w, y: currentY, w: this.prdHlSize.w, h: this.prdHlSize.h } + this.DrawText(this.pGraphics, "RIME", prdSelTextRect, this.textColor) + pBrsh_hlBg := Gdip_BrushCreateSolid(this.hlBgColor) + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlBg, prdHlTextRect.x, prdHlTextRect.y, prdHlTextRect.w, prdHlTextRect.h - 2, r := 2) + Gdip_DeleteBrush(pBrsh_hlBg) + this.DrawText(this.pGraphics, "shu ru fa", prdHlTextRect, this.hlTxtColor) + currentY += this.prdSelSize.h + this.lineSpacing + + ; Draw candidates + for i, candidate in candsArray { + textColor := this.candTxtColor + if (i == selIndex) { ; Draw highlight if selected + textColor := this.hlCandTxtColor + pBrsh_hlCandBg := Gdip_BrushCreateSolid(this.hlCandBgColor) + highlightX := this.borderWidth + this.padding / 2 + highlightY := currentY - this.lineSpacing / 2 + highlightW := this.previewWidth - this.borderWidth * 2 - this.padding + highlightH := this.candSize.h + this.lineSpacing + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, highlightX, highlightY, highlightW, highlightH, r := 4) + Gdip_DeleteBrush(pBrsh_hlCandBg) + } + + textToDraw := i . ". " . candidate + candidateRowRect := { x: this.padding + this.borderWidth, y: currentY, w: this.maxRowWidth, h: this.candSize.h } + this.DrawText(this.pGraphics, textToDraw, candidateRowRect, textColor) + currentY += this.candSize.h + this.lineSpacing + } + + ; Replace preview image with hBitmap + this.hBitmap := Gdip_CreateHBITMAPFromBitmap(this.pBitmap) + SendMessage(STM_SETIMAGE := 0x0172, IMAGE_BITMAP := 0, this.hBitmap, this.imgCtrl.Hwnd) + + this.ReleaseDrawingSurface() + } + + ReleaseFont() { + if (this.hFont) + Gdip_DeleteFont(this.hFont) + if (this.hFamily) + Gdip_DeleteFontFamily(this.hFamily) + if (this.hFormat) + Gdip_DeleteStringFormat(this.hFormat) + } + + ReleaseDrawingSurface() { + if (this.pGraphics) { + Gdip_DeleteGraphics(this.pGraphics) + this.pGraphics := 0 + } + if (this.pBitmap) { + Gdip_DisposeImage(this.pBitmap) + this.pBitmap := 0 + } + if (this.hBitmap) { + DeleteObject(this.hBitmap) + this.hBitmap := 0 + } + } + + ReleaseAll() { + this.ReleaseFont() + this.ReleaseDrawingSurface() + + if (this.pToken) { + Gdip_Shutdown(this.pToken) + this.pToken := 0 + } + } + + MeasureString(pGraphics, text, hFont, hFormat, &RectF) { + rc := Buffer(16) + ; !Notice, this way gets incorrect dim in test + ; dim := Gdip_MeasureString(pGraphics, text, hFont, hFormat, &rc) + ; rect := StrSplit(dim, "|") + ; return { w: Round(rect[3]), h: Round(rect[4]) } + + DllCall("gdiplus\GdipMeasureString", + "Ptr", pGraphics, + "WStr", text, + "Int", -1, + "Ptr", hFont, + "Ptr", RectF.Ptr, + "Ptr", hFormat, + "Ptr", rc.Ptr, + "UInt*", 0, + "UInt*", 0, + "Int") + + return { x: NumGet(rc.Ptr, 0, "Float"), y: NumGet(rc.Ptr, 4, "Float"), + w: NumGet(rc.Ptr, 8, "Float"), h: NumGet(rc.Ptr, 12, "Float") } + } + + DrawText(pGraphics, text, textRect, color) { + this.pBrush := Gdip_BrushCreateSolid(color) + CreateRectF(&RC, textRect.x, textRect.y, textRect.w, textRect.h) + Gdip_DrawString(pGraphics, text, this.hFont, this.hFormat, this.pBrush, &RC) + Gdip_SetTextRenderingHint(this.pGraphics, AntiAlias := 4) + Gdip_DeleteBrush(this.pBrush) + } + + FillRoundedRect(pGraphics, pBrush, x, y, w, h, r) { + if (r <= 0) { + Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) + } else { + Gdip_FillRoundedRectangle(pGraphics, pBrush, x, y, w, h, r) + } + } +} class ThemesGUI { - __New(color_schemes) { - this.preset_color_schemes := color_schemes + __New() { + this.preset_color_schemes := Map() this.colorSchemeMap := Map() - this.previewFontName := "Microsoft YaHei UI" - this.previewFontSize := 12 + this.previewFontName := UIStyle.font_face + this.previewFontSize := UIStyle.font_point this.themeListBoxW := 400 this.previewGroupW := 300 this.previewGroupH := 418 + this.previewGroupOffset := 20 this.currentTheme := "aqua" + this.candsArray := ["输入法", "输入", "数", "书", "输"] this.gui := Gui("+LastFound +OwnDialogs -DPIScale +AlwaysOnTop", "选择主题") + this.gui.MarginX := 10 + this.gui.MarginY := 10 this.gui.SetFont("s10", "Microsoft YaHei UI") this.Build() } - Show() { - this.gui.Show("AutoSize") - } - Build() { - this.gui.Add("Text", "x10 y10", "主题:") - colorChoices := [] + this.preset_color_schemes := this.GetPresetStylesMap() + local colorChoices := [] for key, preset in this.preset_color_schemes { colorChoices.Push(preset["name"]) this.colorSchemeMap[preset["name"]] := key } - themeListBox := this.gui.AddListBox("r15 w" . this.themeListBoxW . " -Multi", colorChoices) - themeListBox.Choose(1) - themeListBox.OnEvent("Change", this.OnChangeColorScheme.Bind(this)) - this.gui.AddGroupBox("x+20 yp-8 w" . this.previewGroupW . " h" . this.previewGroupH . " Section", "预览") - - this.currentTheme := this.colorSchemeMap[themeListBox.Text] - colorOpt := this.GetThemeColor(this.currentTheme) - highlightColorOpt := this.GetThemeColor(this.currentTheme, isHighlight := true) - - this.preeditTxt := " [shu ru fa]" ; ‸ - this.selCandsTxt := " 1. 输入法" - this.candidateTxt := " 2. 输入`n 3. 数`n 4. 书`n 5. 输" - p := this.GetPreviewCandsBoxRect() - pX := p[1], pY := p[2], pW := p[3], pRowH := p[4] - - this.previewGuiPreedit := this.gui.AddText( - Format("xp+{:d} yp+{:d} w{:d} h{:d} {:s}", pX, pY, pW, pRowH, colorOpt), - this.preeditTxt - ) - this.previewGuiSelCand := this.gui.AddText( - Format("xp yp+{:d} w{:d} h{:d} {:s}", pRowH, pW, pRowH, highlightColorOpt), - this.selCandsTxt - ) - this.previewGuiCands := this.gui.AddText( - Format("xp yp+{:d} w{:d} h{:d} {:s}", pRowH, pW, pRowH * 4, colorOpt), - this.candidateTxt - ) - this.SetPreviewCandsBoxFont() + this.gui.Add("Text", "x10 y10", "主题:").GetPos(, , , &titleH) + this.titleH := titleH + + this.themeListBox := this.gui.AddListBox("r15 w" . this.themeListBoxW . " -Multi", colorChoices) + this.themeListBox.Choose(1) + this.themeListBox.OnEvent("Change", this.OnChangeColorScheme.Bind(this)) + this.gui.AddGroupBox(Format("x+{:d} yp-8 w{:d} h{:d} Section", this.previewGroupOffset, this.previewGroupW, this.previewGroupH), "预览") + ; 0xE(SS_BITMAP) or 0x4E (Bitmap and Resizable, but text is unclear) + this.previewImg := this.gui.AddPicture("xp+50 yp+50 w180 h300 0xE BackgroundWhite") - this.setFontBtn := this.gui.Add("Button", "x10 ys+440 w160", "设置字体") - this.confirmBtn := this.gui.Add("Button", "x+400 ys+440 w160", "确定") + this.currentTheme := this.colorSchemeMap[this.themeListBox.Text] + this.SetPreviewCandsBox(this.currentTheme, this.previewFontName, this.previewFontSize) + + this.setFontBtn := this.gui.AddButton("x10 ys+440 w160", "设置字体") + this.confirmBtn := this.gui.AddButton("x+400 ys+440 w160", "确定") this.setFontBtn.OnEvent("Click", this.OnSetFont.Bind(this)) this.confirmBtn.OnEvent("Click", this.OnConfirm.Bind(this)) } + Show() { + this.gui.Show("AutoSize") + } + OnChangeColorScheme(ctrl, info) { if !this.colorSchemeMap.Has(ctrl.Text) return this.currentTheme := this.colorSchemeMap[ctrl.Text] - this.previewGuiPreedit.Opt(this.GetThemeColor(this.currentTheme)) - this.previewGuiSelCand.Opt(this.GetThemeColor(this.currentTheme, darken := true)) - this.previewGuiCands.Opt(this.GetThemeColor(this.currentTheme)) + this.SetPreviewCandsBox(this.currentTheme, this.previewFontName, this.previewFontSize) } OnSetFont(*) { fontGui := Gui("AlwaysOnTop +Owner" this.gui.Hwnd, "字体选择") fontGui.SetFont("s10") - fontGui.Add("Text", "x10 y10", "字体名称:") + fontGui.AddText("x10 y10", "字体名称:") fontChoice := fontGui.AddDropDownList("x+10 yp-4 w180 hp r10", GUIUtilities.GetFontArray()) fontChoice.Text := this.previewFontName - fontGui.Add("Text", "x+30 y10", "大小:") + fontGui.AddText("x+30 y10", "大小:") fontSizeEdit := fontGui.Add("Edit", "x+0 yp-6 w60 Limit2 Number") - fontGui.Add("UpDown", "Range8-20", this.previewFontSize) + fontGui.AddUpDown("Range10-20", this.previewFontSize) - okBtn := fontGui.Add("Button", "x10 yp+30 w120", "确定") - fontGui.Add("Button", "x+150 yp w120", "取消").OnEvent("Click", (*) => fontGui.Destroy()) + okBtn := fontGui.AddButton("x10 yp+30 w120", "确定") + fontGui.AddButton("x+150 yp w120", "取消").OnEvent("Click", (*) => fontGui.Destroy()) okBtn.OnEvent("Click", (*) => ( this.previewFontName := fontChoice.Text, this.previewFontSize := fontSizeEdit.Value, - this.SetPreviewCandsBoxFont(), - this.SetPreviewCandsBoxSize(), + this.SetPreviewCandsBox(this.currentTheme, this.previewFontName, this.previewFontSize), fontGui.Destroy() )) @@ -121,85 +312,74 @@ class ThemesGUI { rime.config_set_string(config, "style/color_scheme", this.currentTheme) rime.config_set_int(config, "style/font_point", this.previewFontSize) rime.config_set_string(config, "style/font_face", this.previewFontName) - UIStyle.Update(config, true) + UIStyle.Update(config, init := true) rime.config_close(config) - box.UpdateUIStyle() + box.UpdateUIStyle() } this.gui.Hide() } - GetPreviewCandsBoxRect(refClient := false) { - previewTxtDim := GUIUtilities.GetTextDim(this.selCandsTxt, this.previewFontName, this.previewFontSize) - previewCandsBoxW := previewTxtDim[1] + 30 - previewCandsBoxRowH := previewTxtDim[2] + 4 - previewCandsBoxX := Round((this.previewGroupW - previewCandsBoxW) / 2) - previewCandsBoxY := Round((this.previewGroupH - previewCandsBoxRowH * 6) / 2) - if refClient { - previewCandsBoxX := previewCandsBoxX + this.themeListBoxW + 30 - previewCandsBoxY := previewCandsBoxY + 40 - } - return [previewCandsBoxX, previewCandsBoxY, previewCandsBoxW, previewCandsBoxRowH] - } - - SetPreviewCandsBoxFont() { - fontSizeOpt := "s" . this.previewFontSize - this.previewGuiPreedit.SetFont(fontSizeOpt, this.previewFontName) - this.previewGuiSelCand.SetFont(fontSizeOpt, this.previewFontName) - this.previewGuiCands.SetFont(fontSizeOpt, this.previewFontName) + SetPreviewCandsBox(theme, fontName, fontSize) { + this.previewStyle := this.GetThemeColor(theme) + this.previewStyle.font_face := fontName + this.previewStyle.font_point := fontSize + candidateBox := CandidatePreview(this.previewImg, this.previewStyle, &candidateBoxW, &candidateBoxH) + previewCandsBoxX := this.gui.MarginX + this.themeListBoxW + this.previewGroupOffset + Round((this.previewGroupW - candidateBoxW) / 2) + previewCandsBoxY := this.gui.MarginY + this.titleH + Round((this.previewGroupH - candidateBoxH) / 2) + this.previewImg.Move(previewCandsBoxX, previewCandsBoxY, candidateBoxW, candidateBoxH) + candidateBox.Render(this.candsArray, 1) } - SetPreviewCandsBoxSize() { - p := this.GetPreviewCandsBoxRect(refClient := true) - newX := p[1], newY := p[2], newW := p[3], newRowH := p[4] - this.previewGuiPreedit.Move(newX, newY, newW, newRowH) - this.previewGuiSelCand.Move(newX, newY + newRowH, newW, newRowH) - this.previewGuiCands.Move(newX, newY + newRowH * 2, newW, newRowH * 4) + GetPresetStylesMap() { + local presetStylesMap := Map() + global rime + if rime and config := rime.config_open("rabbit") { + if iter := rime.config_begin_map(config, "preset_color_schemes") { + while rime.config_next(iter) { + styleMap := Map() + theme := StrLower(iter.key) + if name := rime.config_get_string(config, "preset_color_schemes/" . theme . "/name") { + styleMap["name"] := name + UIStyle.UpdateColor(config, theme) + } + styleMap["border_color"] := UIStyle.border_color + styleMap["text_color"] := UIStyle.text_color + styleMap["back_color"] := UIStyle.back_color + styleMap["hilited_text_color"] := UIStyle.hilited_text_color + styleMap["hilited_back_color"] := UIStyle.hilited_back_color + styleMap["hilited_candidate_text_color"] := UIStyle.hilited_candidate_text_color + styleMap["hilited_candidate_back_color"] := UIStyle.hilited_candidate_back_color + styleMap["candidate_text_color"] := UIStyle.candidate_text_color + styleMap["candidate_back_color"] := UIStyle.candidate_back_color + presetStylesMap[theme] := styleMap + } + rime.config_end(iter) + } + ; restore UIStyle + UIStyle.Update(config, init := true) + rime.config_close(config) + } + return presetStylesMap } - GetThemeColor(selTheme, isHighlight := false) { + GetThemeColor(selTheme) { style := this.preset_color_schemes[selTheme] - text_color := UIStyle.ParseColor(style["text_color"], "abgr", 0xff000000) & 0xffffff - back_color := UIStyle.ParseColor(style["back_color"], "abgr", 0xffeceeee) & 0xffffff - if isHighlight - back_color := DarkenColor(back_color, 15) - - return Format("c{:x} Background{:x}", text_color, back_color) - - DarkenColor(color, percent) { - factor := 1 - (percent / 100) - - r := (color >> 16) & 0xFF - g := (color >> 8) & 0xFF - b := color & 0xFF - - r := Min(Max(Round(r * factor), 0), 255) - g := Min(Max(Round(g * factor), 0), 255) - b := Min(Max(Round(b * factor), 0), 255) - - return (r << 16) | (g << 8) | b + return { + border_color: style["border_color"], + text_color: style["text_color"], + back_color: style["back_color"], + hilited_text_color: style["hilited_text_color"], + hilited_back_color: style["hilited_back_color"], + hilited_candidate_text_color: style["hilited_candidate_text_color"], + hilited_candidate_back_color: style["hilited_candidate_back_color"], + candidate_text_color: style["candidate_text_color"], + candidate_back_color: style["candidate_back_color"], } } } Class GUIUtilities { - static GetTextDim(text, fontName, fontSize) { - hDC := DllCall("GetDC", "UPtr", 0) - ; fontHeight: Round(fontSize * A_ScreenDPI / 72) - nHeight := -DllCall("MulDiv", "Int", fontSize, "Int", DllCall("GetDeviceCaps", "UPtr", hDC, "Int", 90), "Int", 72) - ; fontWeight: regular -> 400 - hFont := DllCall("CreateFont", "Int", nHeight, "Int", 0, "Int", 0, "Int", 0, "Int", fontWeight := 400, "UInt", false, "UInt", false, "UInt", false, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0, "WStr", fontName) - DllCall("SelectObject", "UPtr", hDC, "UPtr", hFont, "UPtr") - DllCall("GetTextExtentPoint32", "ptr", hDC, "WStr", text, "Int", StrLen(text), "int64*", &nSize := 0) - - DllCall("DeleteObject", "Uint", hFont) - DllCall("ReleaseDC", "Uint", 0, "Uint", hDC) - - nWidth := nSize & 0xffffffff - nHeight := nSize >> 32 - return [nWidth, nHeight] - } - static GetFontArray() { static fontArr if isSet(fontArr) @@ -220,4 +400,19 @@ Class GUIUtilities { return true } } + + static GetMonitorDpiScale() { + hr := DllCall( + "Shcore.dll\GetDpiForMonitor", + "ptr", hMonitor := DllCall("MonitorFromPoint", "int64", 0, "uint", 2, "ptr"), + "int", MDT_EFFECTIVE_DPI := 0, + "uint*", &dpiX := 0, + "uint*", &dpiY := 0 + ) + + if (hr != 0) + return 1 + + return dpiX / 96 + } } From 311c28995a8bee4ab46e9c19225c36993caa4a0f Mon Sep 17 00:00:00 2001 From: Tim <67952813+rawbx@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:57:32 +0800 Subject: [PATCH 17/38] feat: rewrite CandidateBox using GDI+ (#19) * refactor themesUI using GDI+ to preivew * fix candidate preview position * rewrite CandidateBox using GDI+ * rewrite CandidateBox using GDI+ * add label and comment colors * align candidate comments --- Lib/Gdip/Gdip_All.ahk | 3106 ++++++++++++++++++++++++++++++++++++ Lib/RabbitCandidateBox.ahk | 322 +++- Lib/RabbitConfig.ahk | 4 +- Lib/RabbitThemesUI.ahk | 16 +- Lib/RabbitUIStyle.ahk | 49 +- Rabbit.ahk | 6 +- 6 files changed, 3468 insertions(+), 35 deletions(-) create mode 100644 Lib/Gdip/Gdip_All.ahk diff --git a/Lib/Gdip/Gdip_All.ahk b/Lib/Gdip/Gdip_All.ahk new file mode 100644 index 0000000..584d593 --- /dev/null +++ b/Lib/Gdip/Gdip_All.ahk @@ -0,0 +1,3106 @@ +; [AHKv2-Gdip](https://github.com/buliasz/AHKv2-Gdip) + +; @tic Created the original Gdip.ahk library. +; @Rseding91 Updated it to make it compatible with unicode and x64 AHK versions and renamed the file Gdip_All.ahk. +; @mmikeww Repository updates @Rseding91's Gdip_All.ahk to fix bugs and make it compatible with AHK v2. +; @buliasz Fork of mmikeww repository: updates for the current version of AHK v2 (dropping AHK v1 backward compatibility). + +; v1.62 +; +;##################################################################################### +;##################################################################################### +; STATUS ENUMERATION +; Return values for functions specified to have status enumerated return type +;##################################################################################### +; +; Ok = 0 +; GenericError = 1 +; InvalidParameter = 2 +; OutOfMemory = 3 +; ObjectBusy = 4 +; InsufficientBuffer = 5 +; NotImplemented = 6 +; Win32Error = 7 +; WrongState = 8 +; Aborted = 9 +; FileNotFound = 10 +; ValueOverflow = 11 +; AccessDenied = 12 +; UnknownImageFormat = 13 +; FontFamilyNotFound = 14 +; FontStyleNotFound = 15 +; NotTrueTypeFont = 16 +; UnsupportedGdiplusVersion = 17 +; GdiplusNotInitialized = 18 +; PropertyNotFound = 19 +; PropertyNotSupported = 20 +; ProfileNotFound = 21 +; +;##################################################################################### +;##################################################################################### +; FUNCTIONS +;##################################################################################### +; +; UpdateLayeredWindow(hwnd, hdc, x:="", y:="", w:="", h:="", Alpha:=255) +; BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster:="") +; StretchBlt(dDC, dx, dy, dw, dh, sDC, sx, sy, sw, sh, Raster:="") +; SetImage(hwnd, hBitmap) +; Gdip_BitmapFromScreen(Screen:=0, Raster:="") +; CreateRectF(&RectF, x, y, w, h) +; CreateSizeF(&SizeF, w, h) +; CreateDIBSection +; +;##################################################################################### + +UpdateLayeredWindow(hwnd, hdc, x:="", y:="", w:="", h:="", Alpha:=255) +{ + if ((x != "") && (y != "")) { + pt := Buffer(8) + NumPut("UInt", x, "UInt", y, pt) + } + + if (w = "") || (h = "") { + WinGetRect(hwnd,,, &w, &h) + } + + return DllCall("UpdateLayeredWindow" + , "UPtr", hwnd + , "UPtr", 0 + , "UPtr", ((x = "") && (y = "")) ? 0 : pt.Ptr + , "Int64*", w|h<<32 + , "UPtr", hdc + , "Int64*", 0 + , "UInt", 0 + , "UInt*", Alpha<<16|1<<24 + , "UInt", 2) +} + +;##################################################################################### + +; Function BitBlt +; Description The BitBlt function performs a bit-block transfer of the color data corresponding to a rectangle +; of pixels from the specified source device context into a destination device context. +; +; dDC handle to destination DC +; dx x-coord of destination upper-left corner +; dy y-coord of destination upper-left corner +; dw width of the area to copy +; dh height of the area to copy +; sDC handle to source DC +; sx x-coordinate of source upper-left corner +; sy y-coordinate of source upper-left corner +; Raster raster operation code +; +; return if the function succeeds, the return value is nonzero +; +; notes if no raster operation is specified, then SRCCOPY is used, which copies the source directly to the destination rectangle +; +; BLACKNESS = 0x00000042 +; NOTSRCERASE = 0x001100A6 +; NOTSRCCOPY = 0x00330008 +; SRCERASE = 0x00440328 +; DSTINVERT = 0x00550009 +; PATINVERT = 0x005A0049 +; SRCINVERT = 0x00660046 +; SRCAND = 0x008800C6 +; MERGEPAINT = 0x00BB0226 +; MERGECOPY = 0x00C000CA +; SRCCOPY = 0x00CC0020 +; SRCPAINT = 0x00EE0086 +; PATCOPY = 0x00F00021 +; PATPAINT = 0x00FB0A09 +; WHITENESS = 0x00FF0062 +; CAPTUREBLT = 0x40000000 +; NOMIRRORBITMAP = 0x80000000 + +BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster:="") +{ + return DllCall("gdi32\BitBlt" + , "UPtr", dDC + , "Int", dx + , "Int", dy + , "Int", dw + , "Int", dh + , "UPtr", sDC + , "Int", sx + , "Int", sy + , "UInt", Raster ? Raster : 0x00CC0020) +} + +;##################################################################################### + +; Function StretchBlt +; Description The StretchBlt function copies a bitmap from a source rectangle into a destination rectangle, +; stretching or compressing the bitmap to fit the dimensions of the destination rectangle, if necessary. +; The system stretches or compresses the bitmap according to the stretching mode currently set in the destination device context. +; +; ddc handle to destination DC +; dx x-coord of destination upper-left corner +; dy y-coord of destination upper-left corner +; dw width of destination rectangle +; dh height of destination rectangle +; sdc handle to source DC +; sx x-coordinate of source upper-left corner +; sy y-coordinate of source upper-left corner +; sw width of source rectangle +; sh height of source rectangle +; Raster raster operation code +; +; return if the function succeeds, the return value is nonzero +; +; notes if no raster operation is specified, then SRCCOPY is used. It uses the same raster operations as BitBlt + +StretchBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, sw, sh, Raster:="") +{ + return DllCall("gdi32\StretchBlt" + , "UPtr", ddc + , "Int", dx + , "Int", dy + , "Int", dw + , "Int", dh + , "UPtr", sdc + , "Int", sx + , "Int", sy + , "Int", sw + , "Int", sh + , "UInt", Raster ? Raster : 0x00CC0020) +} + +;##################################################################################### + +; Function SetStretchBltMode +; Description The SetStretchBltMode function sets the bitmap stretching mode in the specified device context +; +; hdc handle to the DC +; iStretchMode The stretching mode, describing how the target will be stretched +; +; return if the function succeeds, the return value is the previous stretching mode. If it fails it will return 0 +; +; STRETCH_ANDSCANS = 0x01 +; STRETCH_ORSCANS = 0x02 +; STRETCH_DELETESCANS = 0x03 +; STRETCH_HALFTONE = 0x04 + +SetStretchBltMode(hdc, iStretchMode:=4) +{ + return DllCall("gdi32\SetStretchBltMode" + , "UPtr", hdc + , "Int", iStretchMode) +} + +;##################################################################################### + +; Function SetImage +; Description Associates a new image with a static control +; +; hwnd handle of the control to update +; hBitmap a gdi bitmap to associate the static control with +; +; return if the function succeeds, the return value is nonzero + +SetImage(hwnd, hBitmap) +{ + _E := DllCall( "SendMessage", "UPtr", hwnd, "UInt", 0x172, "UInt", 0x0, "UPtr", hBitmap ) + DeleteObject(_E) + return _E +} + +;##################################################################################### + +; Function SetSysColorToControl +; Description Sets a solid colour to a control +; +; hwnd handle of the control to update +; SysColor A system colour to set to the control +; +; return if the function succeeds, the return value is zero +; +; notes A control must have the 0xE style set to it so it is recognised as a bitmap +; By default SysColor=15 is used which is COLOR_3DFACE. This is the standard background for a control +; +; COLOR_3DDKSHADOW = 21 +; COLOR_3DFACE = 15 +; COLOR_3DHIGHLIGHT = 20 +; COLOR_3DHILIGHT = 20 +; COLOR_3DLIGHT = 22 +; COLOR_3DSHADOW = 16 +; COLOR_ACTIVEBORDER = 10 +; COLOR_ACTIVECAPTION = 2 +; COLOR_APPWORKSPACE = 12 +; COLOR_BACKGROUND = 1 +; COLOR_BTNFACE = 15 +; COLOR_BTNHIGHLIGHT = 20 +; COLOR_BTNHILIGHT = 20 +; COLOR_BTNSHADOW = 16 +; COLOR_BTNTEXT = 18 +; COLOR_CAPTIONTEXT = 9 +; COLOR_DESKTOP = 1 +; COLOR_GRADIENTACTIVECAPTION = 27 +; COLOR_GRADIENTINACTIVECAPTION = 28 +; COLOR_GRAYTEXT = 17 +; COLOR_HIGHLIGHT = 13 +; COLOR_HIGHLIGHTTEXT = 14 +; COLOR_HOTLIGHT = 26 +; COLOR_INACTIVEBORDER = 11 +; COLOR_INACTIVECAPTION = 3 +; COLOR_INACTIVECAPTIONTEXT = 19 +; COLOR_INFOBK = 24 +; COLOR_INFOTEXT = 23 +; COLOR_MENU = 4 +; COLOR_MENUHILIGHT = 29 +; COLOR_MENUBAR = 30 +; COLOR_MENUTEXT = 7 +; COLOR_SCROLLBAR = 0 +; COLOR_WINDOW = 5 +; COLOR_WINDOWFRAME = 6 +; COLOR_WINDOWTEXT = 8 + +SetSysColorToControl(hwnd, SysColor:=15) +{ + WinGetRect(hwnd,,, &w, &h) + bc := DllCall("GetSysColor", "Int", SysColor, "UInt") + pBrushClear := Gdip_BrushCreateSolid(0xff000000 | (bc >> 16 | bc & 0xff00 | (bc & 0xff) << 16)) + pBitmap := Gdip_CreateBitmap(w, h), G := Gdip_GraphicsFromImage(pBitmap) + Gdip_FillRectangle(G, pBrushClear, 0, 0, w, h) + hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap) + SetImage(hwnd, hBitmap) + Gdip_DeleteBrush(pBrushClear) + Gdip_DeleteGraphics(G), Gdip_DisposeImage(pBitmap), DeleteObject(hBitmap) + return 0 +} + +;##################################################################################### + +; Function Gdip_BitmapFromScreen +; Description Gets a gdi+ bitmap from the screen +; +; Screen 0 = All screens +; Any numerical value = Just that screen +; x|y|w|h = Take specific coordinates with a width and height +; Raster raster operation code +; +; return if the function succeeds, the return value is a pointer to a gdi+ bitmap +; -1: one or more of x,y,w,h not passed properly +; +; notes if no raster operation is specified, then SRCCOPY is used to the returned bitmap + +Gdip_BitmapFromScreen(Screen:=0, Raster:="") +{ + hhdc := 0 + if (Screen = 0) { + _x := DllCall( "GetSystemMetrics", "Int", 76 ) + _y := DllCall( "GetSystemMetrics", "Int", 77 ) + _w := DllCall( "GetSystemMetrics", "Int", 78 ) + _h := DllCall( "GetSystemMetrics", "Int", 79 ) + } + else if (SubStr(Screen, 1, 5) = "hwnd:") { + Screen := SubStr(Screen, 6) + if !WinExist("ahk_id " Screen) { + return -2 + } + WinGetRect(Screen,,, &_w, &_h) + _x := _y := 0 + hhdc := GetDCEx(Screen, 3) + } + else if IsInteger(Screen) { + M := GetMonitorInfo(Screen) + _x := M.Left, _y := M.Top, _w := M.Right-M.Left, _h := M.Bottom-M.Top + } + else { + S := StrSplit(Screen, "|") + _x := S[1], _y := S[2], _w := S[3], _h := S[4] + } + + if (_x = "") || (_y = "") || (_w = "") || (_h = "") { + return -1 + } + + chdc := CreateCompatibleDC() + hbm := CreateDIBSection(_w, _h, chdc) + obm := SelectObject(chdc, hbm) + hhdc := hhdc ? hhdc : GetDC() + BitBlt(chdc, 0, 0, _w, _h, hhdc, _x, _y, Raster) + ReleaseDC(hhdc) + + pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm) + + SelectObject(chdc, obm) + DeleteObject(hbm) + DeleteDC(hhdc) + DeleteDC(chdc) + return pBitmap +} + +;##################################################################################### + +; Function Gdip_BitmapFromHWND +; Description Uses PrintWindow to get a handle to the specified window and return a bitmap from it +; +; hwnd handle to the window to get a bitmap from +; +; return if the function succeeds, the return value is a pointer to a gdi+ bitmap +; +; notes Window must not be not minimised in order to get a handle to it's client area + +Gdip_BitmapFromHWND(hwnd) +{ + WinGetRect(hwnd,,, &Width, &Height) + hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm) + PrintWindow(hwnd, hdc) + pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm) + SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc) + return pBitmap +} + +;##################################################################################### + +; Function CreateRectF +; Description Creates a RectF object, containing a the coordinates and dimensions of a rectangle +; +; RectF Name to call the RectF object +; x x-coordinate of the upper left corner of the rectangle +; y y-coordinate of the upper left corner of the rectangle +; w Width of the rectangle +; h Height of the rectangle +; +; return No return value + +CreateRectF(&RectF, x, y, w, h) +{ + RectF := Buffer(16) + NumPut( + "Float", x, + "Float", y, + "Float", w, + "Float", h, + RectF) +} + +;##################################################################################### + +; Function CreateRect +; Description Creates a Rect object, containing a the coordinates and dimensions of a rectangle +; +; RectF Name to call the RectF object +; x x-coordinate of the upper left corner of the rectangle +; y y-coordinate of the upper left corner of the rectangle +; w Width of the rectangle +; h Height of the rectangle +; +; return No return value +CreateRect(&Rect, x, y, w, h) +{ + Rect := Buffer(16) + NumPut("UInt", x, "UInt", y, "UInt", w, "UInt", h, Rect) +} +;##################################################################################### + +; Function CreateSizeF +; Description Creates a SizeF object, containing an 2 values +; +; SizeF Name to call the SizeF object +; w w-value for the SizeF object +; h h-value for the SizeF object +; +; return No Return value + +CreateSizeF(&SizeF, w, h) +{ + SizeF := Buffer(8) + NumPut("Float", w, "Float", h, SizeF) +} +;##################################################################################### + +; Function CreatePointF +; Description Creates a SizeF object, containing an 2 values +; +; SizeF Name to call the SizeF object +; w w-value for the SizeF object +; h h-value for the SizeF object +; +; return No Return value + +CreatePointF(&PointF, x, y) +{ + PointF := Buffer(8) + NumPut("Float", x, "Float", y, PointF) +} +;##################################################################################### + +; Function CreateDIBSection +; Description The CreateDIBSection function creates a DIB (Device Independent Bitmap) that applications can write to directly +; +; w width of the bitmap to create +; h height of the bitmap to create +; hdc a handle to the device context to use the palette from +; bpp bits per pixel (32 = ARGB) +; ppvBits A pointer to a variable that receives a pointer to the location of the DIB bit values +; +; return returns a DIB. A gdi bitmap +; +; notes ppvBits will receive the location of the pixels in the DIB + +CreateDIBSection(w, h, hdc:="", bpp:=32, &ppvBits:=0) +{ + hdc2 := hdc ? hdc : GetDC() + bi := Buffer(40, 0) + + NumPut("UInt", 40, "UInt", w, "UInt", h, "ushort", 1, "ushort", bpp, "UInt", 0, bi) + + hbm := DllCall("CreateDIBSection" + , "UPtr", hdc2 + , "UPtr", bi.Ptr + , "UInt", 0 + , "UPtr*", &ppvBits + , "UPtr", 0 + , "UInt", 0, "UPtr") + + if (!hdc) { + ReleaseDC(hdc2) + } + return hbm +} + +;##################################################################################### + +; Function PrintWindow +; Description The PrintWindow function copies a visual window into the specified device context (DC), typically a printer DC +; +; hwnd A handle to the window that will be copied +; hdc A handle to the device context +; Flags Drawing options +; +; return if the function succeeds, it returns a nonzero value +; +; PW_CLIENTONLY = 1 + +PrintWindow(hwnd, hdc, Flags:=0) +{ + return DllCall("PrintWindow", "UPtr", hwnd, "UPtr", hdc, "UInt", Flags) +} + +;##################################################################################### + +; Function DestroyIcon +; Description Destroys an icon and frees any memory the icon occupied +; +; hIcon Handle to the icon to be destroyed. The icon must not be in use +; +; return if the function succeeds, the return value is nonzero + +DestroyIcon(hIcon) +{ + return DllCall("DestroyIcon", "UPtr", hIcon) +} + +;##################################################################################### + +; Function: GetIconDimensions +; Description: Retrieves a given icon/cursor's width and height +; +; hIcon Pointer to an icon or cursor +; Width ByRef variable. This variable is set to the icon's width +; Height ByRef variable. This variable is set to the icon's height +; +; return if the function succeeds, the return value is zero, otherwise: +; -1 = Could not retrieve the icon's info. Check A_LastError for extended information +; -2 = Could not delete the icon's bitmask bitmap +; -3 = Could not delete the icon's color bitmap + +GetIconDimensions(hIcon, &Width:=0, &Height:=0) { + ICONINFO := Buffer(size := 16 + 2 * A_PtrSize, 0) + + if !DllCall("user32\GetIconInfo", "UPtr", hIcon, "UPtr", ICONINFO.Ptr) { + return -1 + } + + hbmMask := NumGet(ICONINFO.Ptr, 16, "UPtr") + hbmColor := NumGet(ICONINFO.Ptr, 16 + A_PtrSize, "UPtr") + BITMAP := Buffer(size, 0) + + if DllCall("gdi32\GetObject", "UPtr", hbmColor, "Int", size, "UPtr", BITMAP.Ptr) { + Width := NumGet(BITMAP.Ptr, 4, "Int") + Height := NumGet(BITMAP.Ptr, 8, "Int") + } + + if !DllCall("gdi32\DeleteObject", "UPtr", hbmMask) { + return -2 + } + + if !DllCall("gdi32\DeleteObject", "UPtr", hbmColor) { + return -3 + } + + return 0 +} + +;##################################################################################### + +PaintDesktop(hdc) +{ + return DllCall("PaintDesktop", "UPtr", hdc) +} + +;##################################################################################### + +CreateCompatibleBitmap(hdc, w, h) +{ + return DllCall("gdi32\CreateCompatibleBitmap", "UPtr", hdc, "Int", w, "Int", h) +} + +;##################################################################################### + +; Function CreateCompatibleDC +; Description This function creates a memory device context (DC) compatible with the specified device +; +; hdc Handle to an existing device context +; +; return returns the handle to a device context or 0 on failure +; +; notes if this handle is 0 (by default), the function creates a memory device context compatible with the application's current screen + +CreateCompatibleDC(hdc:=0) +{ + return DllCall("CreateCompatibleDC", "UPtr", hdc) +} + +;##################################################################################### + +; Function SelectObject +; Description The SelectObject function selects an object into the specified device context (DC). The new object replaces the previous object of the same type +; +; hdc Handle to a DC +; hgdiobj A handle to the object to be selected into the DC +; +; return if the selected object is not a region and the function succeeds, the return value is a handle to the object being replaced +; +; notes The specified object must have been created by using one of the following functions +; Bitmap - CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection (A single bitmap cannot be selected into more than one DC at the same time) +; Brush - CreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrush +; Font - CreateFont, CreateFontIndirect +; Pen - CreatePen, CreatePenIndirect +; Region - CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreateRectRgn, CreateRectRgnIndirect +; +; notes if the selected object is a region and the function succeeds, the return value is one of the following value +; +; SIMPLEREGION = 2 Region consists of a single rectangle +; COMPLEXREGION = 3 Region consists of more than one rectangle +; NULLREGION = 1 Region is empty + +SelectObject(hdc, hgdiobj) +{ + return DllCall("SelectObject", "UPtr", hdc, "UPtr", hgdiobj) +} + +;##################################################################################### + +; Function DeleteObject +; Description This function deletes a logical pen, brush, font, bitmap, region, or palette, freeing all system resources associated with the object +; After the object is deleted, the specified handle is no longer valid +; +; hObject Handle to a logical pen, brush, font, bitmap, region, or palette to delete +; +; return Nonzero indicates success. Zero indicates that the specified handle is not valid or that the handle is currently selected into a device context + +DeleteObject(hObject) +{ + return DllCall("DeleteObject", "UPtr", hObject) +} + +;##################################################################################### + +; Function GetDC +; Description This function retrieves a handle to a display device context (DC) for the client area of the specified window. +; The display device context can be used in subsequent graphics display interface (GDI) functions to draw in the client area of the window. +; +; hwnd Handle to the window whose device context is to be retrieved. If this value is NULL, GetDC retrieves the device context for the entire screen +; +; return The handle the device context for the specified window's client area indicates success. NULL indicates failure + +GetDC(hwnd:=0) +{ + return DllCall("GetDC", "UPtr", hwnd) +} + +;##################################################################################### + +; DCX_CACHE = 0x2 +; DCX_CLIPCHILDREN = 0x8 +; DCX_CLIPSIBLINGS = 0x10 +; DCX_EXCLUDERGN = 0x40 +; DCX_EXCLUDEUPDATE = 0x100 +; DCX_INTERSECTRGN = 0x80 +; DCX_INTERSECTUPDATE = 0x200 +; DCX_LOCKWINDOWUPDATE = 0x400 +; DCX_NORECOMPUTE = 0x100000 +; DCX_NORESETATTRS = 0x4 +; DCX_PARENTCLIP = 0x20 +; DCX_VALIDATE = 0x200000 +; DCX_WINDOW = 0x1 + +GetDCEx(hwnd, flags:=0, hrgnClip:=0) +{ + return DllCall("GetDCEx", "UPtr", hwnd, "UPtr", hrgnClip, "Int", flags) +} + +;##################################################################################### + +; Function ReleaseDC +; Description This function releases a device context (DC), freeing it for use by other applications. The effect of ReleaseDC depends on the type of device context +; +; hdc Handle to the device context to be released +; hwnd Handle to the window whose device context is to be released +; +; return 1 = released +; 0 = not released +; +; notes The application must call the ReleaseDC function for each call to the GetWindowDC function and for each call to the GetDC function that retrieves a common device context +; An application cannot use the ReleaseDC function to release a device context that was created by calling the CreateDC function; instead, it must use the DeleteDC function. + +ReleaseDC(hdc, hwnd:=0) +{ + return DllCall("ReleaseDC", "UPtr", hwnd, "UPtr", hdc) +} + +;##################################################################################### + +; Function DeleteDC +; Description The DeleteDC function deletes the specified device context (DC) +; +; hdc A handle to the device context +; +; return if the function succeeds, the return value is nonzero +; +; notes An application must not delete a DC whose handle was obtained by calling the GetDC function. Instead, it must call the ReleaseDC function to free the DC + +DeleteDC(hdc) +{ + return DllCall("DeleteDC", "UPtr", hdc) +} +;##################################################################################### + +; Function Gdip_LibraryVersion +; Description Get the current library version +; +; return the library version +; +; notes This is useful for non compiled programs to ensure that a person doesn't run an old version when testing your scripts + +Gdip_LibraryVersion() +{ + return 1.45 +} + +;##################################################################################### + +; Function Gdip_LibrarySubVersion +; Description Get the current library sub version +; +; return the library sub version +; +; notes This is the sub-version currently maintained by Rseding91 +; Updated by guest3456 preliminary AHK v2 support +Gdip_LibrarySubVersion() +{ + return 1.54 +} + +;##################################################################################### + +; Function: Gdip_BitmapFromBRA +; Description: Gets a pointer to a gdi+ bitmap from a BRA file +; +; BRAFromMemIn The variable for a BRA file read to memory +; File The name of the file, or its number that you would like (This depends on alternate parameter) +; Alternate Changes whether the File parameter is the file name or its number +; +; return if the function succeeds, the return value is a pointer to a gdi+ bitmap +; -1 = The BRA variable is empty +; -2 = The BRA has an incorrect header +; -3 = The BRA has information missing +; -4 = Could not find file inside the BRA + +Gdip_BitmapFromBRA(BRAFromMemIn, File, Alternate := 0) { + if (!BRAFromMemIn) { + return -1 + } + + Headers := StrSplit(StrGet(BRAFromMemIn.Ptr, 256, "CP0"), "`n") + Header := StrSplit(Headers[1], "|") + HeaderLength := Header.Length + + if (HeaderLength != 4) || (Header[2] != "BRA!") { + return -2 + } + + _Info := StrSplit(Headers[2], "|") + _InfoLength := _Info.Length + + if (_InfoLength != 3) { + return -3 + } + + OffsetTOC := StrPut(Headers[1], "CP0") + StrPut(Headers[2], "CP0") ; + 2 + OffsetData := _Info[2] + SearchIndex := Alternate ? 1 : 2 + TOC := StrGet(BRAFromMemIn.Ptr + OffsetTOC, OffsetData - OffsetTOC - 1, "CP0") + RX1 := "mi`n)^" + Offset := Size := 0 + + if RegExMatch(TOC, RX1 . (Alternate ? File "\|.+?" : "\d+\|" . File) . "\|(\d+)\|(\d+)$", &FileInfo:="") { + Offset := OffsetData + FileInfo[1] + Size := FileInfo[2] + } + + if (Size = 0) { + return -4 + } + + hData := DllCall("GlobalAlloc", "UInt", 2, "UInt", Size, "UPtr") + pData := DllCall("GlobalLock", "Ptr", hData, "UPtr") + DllCall("RtlMoveMemory", "Ptr", pData, "Ptr", BRAFromMemIn.Ptr + Offset, "Ptr", Size) + DllCall("GlobalUnlock", "Ptr", hData) + DllCall("Ole32.dll\CreateStreamOnHGlobal", "Ptr", hData, "Int", 1, "Ptr*", &pStream:=0) + DllCall("Gdiplus.dll\GdipCreateBitmapFromStream", "Ptr", pStream, "Ptr*", &pBitmap:=0) + ObjRelease(pStream) + + return pBitmap +} + +;##################################################################################### + +; Function: Gdip_BitmapFromBase64 +; Description: Creates a bitmap from a Base64 encoded string +; +; Base64 ByRef variable. Base64 encoded string. Immutable, ByRef to avoid performance overhead of passing long strings. +; +; return if the function succeeds, the return value is a pointer to a bitmap, otherwise: +; -1 = Could not calculate the length of the required buffer +; -2 = Could not decode the Base64 encoded string +; -3 = Could not create a memory stream + +Gdip_BitmapFromBase64(&Base64) +{ + ; calculate the length of the buffer needed + if !(DllCall("crypt32\CryptStringToBinary", "UPtr", StrPtr(Base64), "UInt", 0, "UInt", 0x01, "UPtr", 0, "UInt*", &DecLen:=0, "UPtr", 0, "UPtr", 0)) { + return -1 + } + + Dec := Buffer(DecLen, 0) + + ; decode the Base64 encoded string + if !(DllCall("crypt32\CryptStringToBinary", "UPtr", StrPtr(Base64), "UInt", 0, "UInt", 0x01, "UPtr", Dec.Ptr, "UInt*", &DecLen, "UPtr", 0, "UPtr", 0)) { + return -2 + } + + ; create a memory stream + if !(pStream := DllCall("shlwapi\SHCreateMemStream", "UPtr", Dec.Ptr, "UInt", DecLen, "UPtr")) { + return -3 + } + + DllCall("gdiplus\GdipCreateBitmapFromStreamICM", "UPtr", pStream, "Ptr*", &pBitmap:=0) + ObjRelease(pStream) + + return pBitmap +} + +;##################################################################################### + +; Function: Gdip_EncodeBitmapTo64string +; Description: Encode a bitmap to a Base64 encoded string +; +; pBitmap Pointer to a bitmap +; sOutput The name of the file that the bitmap will be saved to. Supported extensions are: .BMP,.DIB,.RLE,.JPG,.JPEG,.JPE,.JFIF,.GIF,.TIF,.TIFF,.PNG +; Quality if saving as jpg (.JPG,.JPEG,.JPE,.JFIF) then quality can be 1-100 with default at maximum quality +; +; return if the function succeeds, the return value is a Base64 encoded string of the pBitmap + +Gdip_EncodeBitmapTo64string(pBitmap, extension := "png", quality := "") { + + ; Fill a buffer with the available image codec info. + DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &count:=0, "uint*", &size:=0) + DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", ci := Buffer(size)) + + ; struct ImageCodecInfo - http://www.jose.it-berater.org/gdiplus/reference/structures/imagecodecinfo.htm + loop { + if (A_Index > count) + throw Error("Could not find a matching encoder for the specified file format.") + + idx := (48+7*A_PtrSize)*(A_Index-1) + } until InStr(StrGet(NumGet(ci, idx+32+3*A_PtrSize, "ptr"), "UTF-16"), extension) ; FilenameExtension + + ; Get the pointer to the clsid of the matching encoder. + pCodec := ci.ptr + idx ; ClassID + + ; JPEG default quality is 75. Otherwise set a quality value from [0-100]. + if (quality ~= "^-?\d+$") and ("image/jpeg" = StrGet(NumGet(ci, idx+32+4*A_PtrSize, "ptr"), "UTF-16")) { ; MimeType + ; Use a separate buffer to store the quality as ValueTypeLong (4). + v := Buffer(4) + NumPut("uint", quality, v) + + ; struct EncoderParameter - http://www.jose.it-berater.org/gdiplus/reference/structures/encoderparameter.htm + ; enum ValueType - https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparametervaluetype + ; clsid Image Encoder Constants - http://www.jose.it-berater.org/gdiplus/reference/constants/gdipimageencoderconstants.htm + ep := Buffer(24+2*A_PtrSize) ; sizeof(EncoderParameter) = ptr + n*(28, 32) + NumPut( "uptr", 1, ep, 0) ; Count + DllCall("ole32\CLSIDFromString", "wstr", "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}", "ptr", ep.ptr+A_PtrSize, "HRESULT") + NumPut( "uint", 1, ep, 16+A_PtrSize) ; Number of Values + NumPut( "uint", 4, ep, 20+A_PtrSize) ; Type + NumPut( "ptr", v.ptr, ep, 24+A_PtrSize) ; Value + } + + ; Create a Stream. + DllCall("ole32\CreateStreamOnHGlobal", "ptr", 0, "int", True, "ptr*", &pStream:=0, "HRESULT") + DllCall("gdiplus\GdipSaveImageToStream", "ptr", pBitmap, "ptr", pStream, "ptr", pCodec, "ptr", IsSet(ep) ? ep : 0) + + ; Get a pointer to binary data. + DllCall("ole32\GetHGlobalFromStream", "ptr", pStream, "ptr*", &hbin:=0, "HRESULT") + bin := DllCall("GlobalLock", "ptr", hbin, "ptr") + size := DllCall("GlobalSize", "uint", bin, "uptr") + + ; Calculate the length of the base64 string. + flags := 0x40000001 ; CRYPT_STRING_NOCRLF | CRYPT_STRING_BASE64 + length := 4 * Ceil(size/3) + 1 ; An extra byte of padding is required. + str := Buffer(length) + + ; Using CryptBinaryToStringA saves about 2MB in memory. + DllCall("crypt32\CryptBinaryToStringA", "ptr", bin, "uint", size, "uint", flags, "ptr", str, "uint*", &length) + + ; Release binary data and stream. + DllCall("GlobalUnlock", "ptr", hbin) + ObjRelease(pStream) + + ; Return encoded string length minus 1. + return StrGet(str, length, "CP0") +} + +;##################################################################################### + +; Function Gdip_DrawRectangle +; Description This function uses a pen to draw the outline of a rectangle into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x x-coordinate of the top left of the rectangle +; y y-coordinate of the top left of the rectangle +; w width of the rectanlge +; h height of the rectangle +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h) +{ + return DllCall("gdiplus\GdipDrawRectangle", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h) +} + +;##################################################################################### + +; Function Gdip_DrawRoundedRectangle +; Description This function uses a pen to draw the outline of a rounded rectangle into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x x-coordinate of the top left of the rounded rectangle +; y y-coordinate of the top left of the rounded rectangle +; w width of the rectanlge +; h height of the rectangle +; r radius of the rounded corners +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawRoundedRectangle(pGraphics, pPen, x, y, w, h, r) +{ + Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4) + _E := Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h) + Gdip_ResetClip(pGraphics) + Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4) + Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4) + Gdip_DrawEllipse(pGraphics, pPen, x, y, 2*r, 2*r) + Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y, 2*r, 2*r) + Gdip_DrawEllipse(pGraphics, pPen, x, y+h-(2*r), 2*r, 2*r) + Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y+h-(2*r), 2*r, 2*r) + Gdip_ResetClip(pGraphics) + return _E +} + +;##################################################################################### + +; Function Gdip_DrawEllipse +; Description This function uses a pen to draw the outline of an ellipse into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x x-coordinate of the top left of the rectangle the ellipse will be drawn into +; y y-coordinate of the top left of the rectangle the ellipse will be drawn into +; w width of the ellipse +; h height of the ellipse +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawEllipse(pGraphics, pPen, x, y, w, h) +{ + return DllCall("gdiplus\GdipDrawEllipse", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h) +} + +;##################################################################################### + +; Function Gdip_DrawBezier +; Description This function uses a pen to draw the outline of a bezier (a weighted curve) into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x1 x-coordinate of the start of the bezier +; y1 y-coordinate of the start of the bezier +; x2 x-coordinate of the first arc of the bezier +; y2 y-coordinate of the first arc of the bezier +; x3 x-coordinate of the second arc of the bezier +; y3 y-coordinate of the second arc of the bezier +; x4 x-coordinate of the end of the bezier +; y4 y-coordinate of the end of the bezier +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawBezier(pGraphics, pPen, x1, y1, x2, y2, x3, y3, x4, y4) +{ + return DllCall("gdiplus\GdipDrawBezier" + , "UPtr", pgraphics + , "UPtr", pPen + , "Float", x1 + , "Float", y1 + , "Float", x2 + , "Float", y2 + , "Float", x3 + , "Float", y3 + , "Float", x4 + , "Float", y4) +} + +;##################################################################################### + +; Function Gdip_DrawArc +; Description This function uses a pen to draw the outline of an arc into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x x-coordinate of the start of the arc +; y y-coordinate of the start of the arc +; w width of the arc +; h height of the arc +; StartAngle specifies the angle between the x-axis and the starting point of the arc +; SweepAngle specifies the angle between the starting and ending points of the arc +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawArc(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle) +{ + return DllCall("gdiplus\GdipDrawArc" + , "UPtr", pGraphics + , "UPtr", pPen + , "Float", x + , "Float", y + , "Float", w + , "Float", h + , "Float", StartAngle + , "Float", SweepAngle) +} + +;##################################################################################### + +; Function Gdip_DrawPie +; Description This function uses a pen to draw the outline of a pie into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x x-coordinate of the start of the pie +; y y-coordinate of the start of the pie +; w width of the pie +; h height of the pie +; StartAngle specifies the angle between the x-axis and the starting point of the pie +; SweepAngle specifies the angle between the starting and ending points of the pie +; +; return status enumeration. 0 = success +; +; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width + +Gdip_DrawPie(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle) +{ + return DllCall("gdiplus\GdipDrawPie", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h, "Float", StartAngle, "Float", SweepAngle) +} + +;##################################################################################### + +; Function Gdip_DrawLine +; Description This function uses a pen to draw a line into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; x1 x-coordinate of the start of the line +; y1 y-coordinate of the start of the line +; x2 x-coordinate of the end of the line +; y2 y-coordinate of the end of the line +; +; return status enumeration. 0 = success + +Gdip_DrawLine(pGraphics, pPen, x1, y1, x2, y2) +{ + return DllCall("gdiplus\GdipDrawLine" + , "UPtr", pGraphics + , "UPtr", pPen + , "Float", x1 + , "Float", y1 + , "Float", x2 + , "Float", y2) +} + +;##################################################################################### + +; Function Gdip_DrawLines +; Description This function uses a pen to draw a series of joined lines into the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pPen Pointer to a pen +; Points the coordinates of all the points passed as x1,y1|x2,y2|x3,y3..... +; +; return status enumeration. 0 = success + +Gdip_DrawLines(pGraphics, pPen, points) +{ + points := StrSplit(points, "|") + pointF := Buffer(8*points.Length) + pointsLength := 0 + for point in points { + coords := StrSplit(point, ",") + if (coords.Length != 2) { + if (coords.Length > 0) { + MsgBox("Skipping wrong points of length " coords.Length) + } + continue + } + NumPut("Float", coords[1], pointF, 8*(A_Index-1)) + NumPut("Float", coords[2], pointF, (8*(A_Index-1))+4) + pointsLength += 1 + } + return DllCall("gdiplus\GdipDrawLines", "UPtr", pGraphics, "UPtr", pPen, "UPtr", pointF.Ptr, "Int", pointsLength) +} + +;##################################################################################### + +; Function Gdip_FillRectangle +; Description This function uses a brush to fill a rectangle in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; x x-coordinate of the top left of the rectangle +; y y-coordinate of the top left of the rectangle +; w width of the rectanlge +; h height of the rectangle +; +; return status enumeration. 0 = success + +Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) +{ + return DllCall("gdiplus\GdipFillRectangle" + , "UPtr", pGraphics + , "UPtr", pBrush + , "Float", x + , "Float", y + , "Float", w + , "Float", h) +} + +;##################################################################################### + +; Function Gdip_FillRoundedRectangle +; Description This function uses a brush to fill a rounded rectangle in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; x x-coordinate of the top left of the rounded rectangle +; y y-coordinate of the top left of the rounded rectangle +; w width of the rectanlge +; h height of the rectangle +; r radius of the rounded corners +; +; return status enumeration. 0 = success + +Gdip_FillRoundedRectangle(pGraphics, pBrush, x, y, w, h, r) +{ + Region := Gdip_GetClipRegion(pGraphics) + Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4) + Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4) + _E := Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) + Gdip_SetClipRegion(pGraphics, Region, 0) + Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4) + Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4) + Gdip_FillEllipse(pGraphics, pBrush, x, y, 2*r, 2*r) + Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y, 2*r, 2*r) + Gdip_FillEllipse(pGraphics, pBrush, x, y+h-(2*r), 2*r, 2*r) + Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y+h-(2*r), 2*r, 2*r) + Gdip_SetClipRegion(pGraphics, Region, 0) + Gdip_DeleteRegion(Region) + return _E +} + +;##################################################################################### + +; Function Gdip_FillPolygon +; Description This function uses a brush to fill a polygon in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; Points the coordinates of all the points passed as x1,y1|x2,y2|x3,y3..... +; +; return status enumeration. 0 = success +; +; notes Alternate will fill the polygon as a whole, wheras winding will fill each new "segment" +; Alternate = 0 +; Winding = 1 + +Gdip_FillPolygon(pGraphics, pBrush, Points, FillMode:=0) +{ + Points := StrSplit(Points, "|") + PointsLength := Points.Length + PointF := Buffer(8*PointsLength) + For eachPoint, Point in Points + { + Coord := StrSplit(Point, ",") + NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) + NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) + } + return DllCall("gdiplus\GdipFillPolygon", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", PointF.Ptr, "Int", PointsLength, "Int", FillMode) +} + +;##################################################################################### + +; Function Gdip_FillPie +; Description This function uses a brush to fill a pie in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; x x-coordinate of the top left of the pie +; y y-coordinate of the top left of the pie +; w width of the pie +; h height of the pie +; StartAngle specifies the angle between the x-axis and the starting point of the pie +; SweepAngle specifies the angle between the starting and ending points of the pie +; +; return status enumeration. 0 = success + +Gdip_FillPie(pGraphics, pBrush, x, y, w, h, StartAngle, SweepAngle) +{ + return DllCall("gdiplus\GdipFillPie" + , "UPtr", pGraphics + , "UPtr", pBrush + , "Float", x + , "Float", y + , "Float", w + , "Float", h + , "Float", StartAngle + , "Float", SweepAngle) +} + +;##################################################################################### + +; Function Gdip_FillEllipse +; Description This function uses a brush to fill an ellipse in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; x x-coordinate of the top left of the ellipse +; y y-coordinate of the top left of the ellipse +; w width of the ellipse +; h height of the ellipse +; +; return status enumeration. 0 = success + +Gdip_FillEllipse(pGraphics, pBrush, x, y, w, h) +{ + return DllCall("gdiplus\GdipFillEllipse", "UPtr", pGraphics, "UPtr", pBrush, "Float", x, "Float", y, "Float", w, "Float", h) +} + +;##################################################################################### + +; Function Gdip_FillRegion +; Description This function uses a brush to fill a region in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; Region Pointer to a Region +; +; return status enumeration. 0 = success +; +; notes You can create a region Gdip_CreateRegion() and then add to this + +Gdip_FillRegion(pGraphics, pBrush, Region) +{ + return DllCall("gdiplus\GdipFillRegion", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", Region) +} + +;##################################################################################### + +; Function Gdip_FillPath +; Description This function uses a brush to fill a path in the Graphics of a bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBrush Pointer to a brush +; Region Pointer to a Path +; +; return status enumeration. 0 = success + +Gdip_FillPath(pGraphics, pBrush, pPath) +{ + return DllCall("gdiplus\GdipFillPath", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", pPath) +} + +;##################################################################################### + +; Function Gdip_DrawImagePointsRect +; Description This function draws a bitmap into the Graphics of another bitmap and skews it +; +; pGraphics Pointer to the Graphics of a bitmap +; pBitmap Pointer to a bitmap to be drawn +; Points Points passed as x1,y1|x2,y2|x3,y3 (3 points: top left, top right, bottom left) describing the drawing of the bitmap +; sx x-coordinate of source upper-left corner +; sy y-coordinate of source upper-left corner +; sw width of source rectangle +; sh height of source rectangle +; Matrix a matrix used to alter image attributes when drawing +; +; return status enumeration. 0 = success +; +; notes if sx,sy,sw,sh are missed then the entire source bitmap will be used +; Matrix can be omitted to just draw with no alteration to ARGB +; Matrix may be passed as a digit from 0 - 1 to change just transparency +; Matrix can be passed as a matrix with any delimiter + +Gdip_DrawImagePointsRect(pGraphics, pBitmap, Points, sx:="", sy:="", sw:="", sh:="", Matrix:=1) +{ + Points := StrSplit(Points, "|") + PointsLength := Points.Length + PointF := Buffer(8*PointsLength) + For eachPoint, Point in Points + { + Coord := StrSplit(Point, ",") + NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) + NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) + } + + if !IsNumber(Matrix) + ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix) + else if (Matrix != 1) + ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1") + else + ImageAttr := 0 + + if (sx = "" && sy = "" && sw = "" && sh = "") + { + sx := 0, sy := 0 + sw := Gdip_GetImageWidth(pBitmap) + sh := Gdip_GetImageHeight(pBitmap) + } + + _E := DllCall("gdiplus\GdipDrawImagePointsRect" + , "UPtr", pGraphics + , "UPtr", pBitmap + , "UPtr", PointF.Ptr + , "Int", PointsLength + , "Float", sx + , "Float", sy + , "Float", sw + , "Float", sh + , "Int", 2 + , "UPtr", ImageAttr + , "UPtr", 0 + , "UPtr", 0) + if ImageAttr + Gdip_DisposeImageAttributes(ImageAttr) + return _E +} + +;##################################################################################### + +; Function Gdip_DrawImage +; Description This function draws a bitmap into the Graphics of another bitmap +; +; pGraphics Pointer to the Graphics of a bitmap +; pBitmap Pointer to a bitmap to be drawn +; dx x-coord of destination upper-left corner +; dy y-coord of destination upper-left corner +; dw width of destination image +; dh height of destination image +; sx x-coordinate of source upper-left corner +; sy y-coordinate of source upper-left corner +; sw width of source image +; sh height of source image +; Matrix a matrix used to alter image attributes when drawing +; +; return status enumeration. 0 = success +; +; notes if sx,sy,sw,sh are missed then the entire source bitmap will be used +; Gdip_DrawImage performs faster +; Matrix can be omitted to just draw with no alteration to ARGB +; Matrix may be passed as a digit from 0 - 1 to change just transparency +; Matrix can be passed as a matrix with any delimiter. For example: +; MatrixBright= +; ( +; 1.5 |0 |0 |0 |0 +; 0 |1.5 |0 |0 |0 +; 0 |0 |1.5 |0 |0 +; 0 |0 |0 |1 |0 +; 0.05 |0.05 |0.05 |0 |1 +; ) +; +; notes MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1 +; MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1 +; MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|1|1|1|0|1 + +Gdip_DrawImage(pGraphics, pBitmap, dx:="", dy:="", dw:="", dh:="", sx:="", sy:="", sw:="", sh:="", Matrix:=1) +{ + if !IsNumber(Matrix) + ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix) + else if (Matrix != 1) + ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1") + else + ImageAttr := 0 + + if (sx = "" && sy = "" && sw = "" && sh = "") + { + if (dx = "" && dy = "" && dw = "" && dh = "") + { + sx := dx := 0, sy := dy := 0 + sw := dw := Gdip_GetImageWidth(pBitmap) + sh := dh := Gdip_GetImageHeight(pBitmap) + } + else + { + sx := sy := 0 + sw := Gdip_GetImageWidth(pBitmap) + sh := Gdip_GetImageHeight(pBitmap) + } + } + + _E := DllCall("gdiplus\GdipDrawImageRectRect" + , "UPtr", pGraphics + , "UPtr", pBitmap + , "Float", dx + , "Float", dy + , "Float", dw + , "Float", dh + , "Float", sx + , "Float", sy + , "Float", sw + , "Float", sh + , "Int", 2 + , "UPtr", ImageAttr + , "UPtr", 0 + , "UPtr", 0) + if ImageAttr + Gdip_DisposeImageAttributes(ImageAttr) + return _E +} + +;##################################################################################### + +; Function Gdip_SetImageAttributesColorMatrix +; Description This function creates an image matrix ready for drawing +; +; Matrix a matrix used to alter image attributes when drawing +; passed with any delimeter +; +; return returns an image matrix on sucess or 0 if it fails +; +; notes MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1 +; MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1 +; MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|1|1|1|0|1 + +Gdip_SetImageAttributesColorMatrix(Matrix) +{ + ColourMatrix := Buffer(100, 0) + Matrix := RegExReplace(RegExReplace(Matrix, "^[^\d-\.]+([\d\.])", "$1", , 1), "[^\d-\.]+", "|") + Matrix := StrSplit(Matrix, "|") + + loop 25 { + M := (Matrix[A_Index] != "") ? Matrix[A_Index] : Mod(A_Index-1, 6) ? 0 : 1 + NumPut("Float", M, ColourMatrix, (A_Index-1)*4) + } + + DllCall("gdiplus\GdipCreateImageAttributes", "UPtr*", &ImageAttr:=0) + DllCall("gdiplus\GdipSetImageAttributesColorMatrix", "UPtr", ImageAttr, "Int", 1, "Int", 1, "UPtr", ColourMatrix.Ptr, "UPtr", 0, "Int", 0) + + return ImageAttr +} + +;##################################################################################### + +; Function Gdip_GraphicsFromImage +; Description This function gets the graphics for a bitmap used for drawing functions +; +; pBitmap Pointer to a bitmap to get the pointer to its graphics +; +; return returns a pointer to the graphics of a bitmap +; +; notes a bitmap can be drawn into the graphics of another bitmap + +Gdip_GraphicsFromImage(pBitmap) +{ + DllCall("gdiplus\GdipGetImageGraphicsContext", "UPtr", pBitmap, "UPtr*", &pGraphics:=0) + return pGraphics +} + +;##################################################################################### + +; Function Gdip_GraphicsFromHDC +; Description This function gets the graphics from the handle to a device context +; +; hdc This is the handle to the device context +; +; return returns a pointer to the graphics of a bitmap +; +; notes You can draw a bitmap into the graphics of another bitmap + +Gdip_GraphicsFromHDC(hdc) +{ + DllCall("gdiplus\GdipCreateFromHDC", "UPtr", hdc, "UPtr*", &pGraphics:=0) + return pGraphics +} + +;##################################################################################### + +; Function Gdip_GetDC +; Description This function gets the device context of the passed Graphics +; +; hdc This is the handle to the device context +; +; return returns the device context for the graphics of a bitmap + +Gdip_GetDC(pGraphics) +{ + DllCall("gdiplus\GdipGetDC", "UPtr", pGraphics, "UPtr*", &hdc:=0) + return hdc +} + +;##################################################################################### + +; Function Gdip_ReleaseDC +; Description This function releases a device context from use for further use +; +; pGraphics Pointer to the graphics of a bitmap +; hdc This is the handle to the device context +; +; return status enumeration. 0 = success + +Gdip_ReleaseDC(pGraphics, hdc) +{ + return DllCall("gdiplus\GdipReleaseDC", "UPtr", pGraphics, "UPtr", hdc) +} + +;##################################################################################### + +; Function Gdip_GraphicsClear +; Description Clears the graphics of a bitmap ready for further drawing +; +; pGraphics Pointer to the graphics of a bitmap +; ARGB The colour to clear the graphics to +; +; return status enumeration. 0 = success +; +; notes By default this will make the background invisible +; Using clipping regions you can clear a particular area on the graphics rather than clearing the entire graphics + +Gdip_GraphicsClear(pGraphics, ARGB:=0x00ffffff) +{ + return DllCall("gdiplus\GdipGraphicsClear", "UPtr", pGraphics, "Int", ARGB) +} + +;##################################################################################### + +; Function Gdip_BlurBitmap +; Description Gives a pointer to a blurred bitmap from a pointer to a bitmap +; +; pBitmap Pointer to a bitmap to be blurred +; Blur The Amount to blur a bitmap by from 1 (least blur) to 100 (most blur) +; +; return if the function succeeds, the return value is a pointer to the new blurred bitmap +; -1 = The blur parameter is outside the range 1-100 +; +; notes This function will not dispose of the original bitmap + +Gdip_BlurBitmap(pBitmap, Blur) +{ + if (Blur > 100 || Blur < 1) { + return -1 + } + + sWidth := Gdip_GetImageWidth(pBitmap), sHeight := Gdip_GetImageHeight(pBitmap) + dWidth := sWidth//Blur, dHeight := sHeight//Blur + + pBitmap1 := Gdip_CreateBitmap(dWidth, dHeight) + G1 := Gdip_GraphicsFromImage(pBitmap1) + Gdip_SetInterpolationMode(G1, 7) + Gdip_DrawImage(G1, pBitmap, 0, 0, dWidth, dHeight, 0, 0, sWidth, sHeight) + + Gdip_DeleteGraphics(G1) + + pBitmap2 := Gdip_CreateBitmap(sWidth, sHeight) + G2 := Gdip_GraphicsFromImage(pBitmap2) + Gdip_SetInterpolationMode(G2, 7) + Gdip_DrawImage(G2, pBitmap1, 0, 0, sWidth, sHeight, 0, 0, dWidth, dHeight) + + Gdip_DeleteGraphics(G2) + Gdip_DisposeImage(pBitmap1) + + return pBitmap2 +} + +;##################################################################################### + +; Function: Gdip_SaveBitmapToFile +; Description: Saves a bitmap to a file in any supported format onto disk +; +; pBitmap Pointer to a bitmap +; sOutput The name of the file that the bitmap will be saved to. Supported extensions are: .BMP,.DIB,.RLE,.JPG,.JPEG,.JPE,.JFIF,.GIF,.TIF,.TIFF,.PNG +; Quality if saving as jpg (.JPG,.JPEG,.JPE,.JFIF) then quality can be 1-100 with default at maximum quality +; +; return if the function succeeds, the return value is zero, otherwise: +; -1 = Extension supplied is not a supported file format +; -2 = Could not get a list of encoders on system +; -3 = Could not find matching encoder for specified file format +; -4 = Could not get WideChar name of output file +; -5 = Could not save file to disk +; +; notes This function will use the extension supplied from the sOutput parameter to determine the output format + +Gdip_SaveBitmapToFile(pBitmap, sOutput, Quality:=75) +{ + _p := 0 + + SplitPath sOutput,,, &extension:="" + if (!RegExMatch(extension, "^(?i:BMP|DIB|RLE|JPG|JPEG|JPE|JFIF|GIF|TIF|TIFF|PNG)$")) { + return -1 + } + extension := "." extension + + DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &nCount:=0, "uint*", &nSize:=0) + ci := Buffer(nSize) + DllCall("gdiplus\GdipGetImageEncoders", "UInt", nCount, "UInt", nSize, "UPtr", ci.Ptr) + if !(nCount && nSize) { + return -2 + } + + loop nCount { + address := NumGet(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "UPtr") + sString := StrGet(address, "UTF-16") + if !InStr(sString, "*" extension) + continue + + pCodec := ci.Ptr+idx + break + } + + if !pCodec { + return -3 + } + + if (Quality != 75) { + Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality + + if RegExMatch(extension, "^\.(?i:JPG|JPEG|JPE|JFIF)$") { + DllCall("gdiplus\GdipGetEncoderParameterListSize", "UPtr", pBitmap, "UPtr", pCodec, "uint*", &nSize) + EncoderParameters := Buffer(nSize, 0) + DllCall("gdiplus\GdipGetEncoderParameterList", "UPtr", pBitmap, "UPtr", pCodec, "UInt", nSize, "UPtr", EncoderParameters.Ptr) + nCount := NumGet(EncoderParameters, "UInt") + loop nCount + { + elem := (24+(A_PtrSize ? A_PtrSize : 4))*(A_Index-1) + 4 + (pad := A_PtrSize = 8 ? 4 : 0) + if (NumGet(EncoderParameters, elem+16, "UInt") = 1) && (NumGet(EncoderParameters, elem+20, "UInt") = 6) + { + _p := elem + EncoderParameters.Ptr - pad - 4 + NumPut("UInt", Quality, NumGet(NumPut("UInt", 4, NumPut("UInt", 1, _p+0)+20), "UInt")) + break + } + } + } + } + + _E := DllCall("gdiplus\GdipSaveImageToFile", "UPtr", pBitmap, "UPtr", StrPtr(sOutput), "UPtr", pCodec, "UInt", _p ? _p : 0) + + return _E ? -5 : 0 +} + +;##################################################################################### + +; Function Gdip_GetPixel +; Description Gets the ARGB of a pixel in a bitmap +; +; pBitmap Pointer to a bitmap +; x x-coordinate of the pixel +; y y-coordinate of the pixel +; +; return Returns the ARGB value of the pixel + +Gdip_GetPixel(pBitmap, x, y) +{ + DllCall("gdiplus\GdipBitmapGetPixel", "UPtr", pBitmap, "Int", x, "Int", y, "uint*", &ARGB:=0) + return ARGB +} + +;##################################################################################### + +; Function Gdip_SetPixel +; Description Sets the ARGB of a pixel in a bitmap +; +; pBitmap Pointer to a bitmap +; x x-coordinate of the pixel +; y y-coordinate of the pixel +; +; return status enumeration. 0 = success + +Gdip_SetPixel(pBitmap, x, y, ARGB) +{ + return DllCall("gdiplus\GdipBitmapSetPixel", "UPtr", pBitmap, "Int", x, "Int", y, "Int", ARGB) +} + +;##################################################################################### + +; Function Gdip_GetImageWidth +; Description Gives the width of a bitmap +; +; pBitmap Pointer to a bitmap +; +; return Returns the width in pixels of the supplied bitmap + +Gdip_GetImageWidth(pBitmap) +{ + DllCall("gdiplus\GdipGetImageWidth", "UPtr", pBitmap, "uint*", &Width:=0) + return Width +} + +;##################################################################################### + +; Function Gdip_GetImageHeight +; Description Gives the height of a bitmap +; +; pBitmap Pointer to a bitmap +; +; return Returns the height in pixels of the supplied bitmap + +Gdip_GetImageHeight(pBitmap) +{ + DllCall("gdiplus\GdipGetImageHeight", "UPtr", pBitmap, "uint*", &Height:=0) + return Height +} + +;##################################################################################### + +; Function Gdip_GetDimensions +; Description Gives the width and height of a bitmap +; +; pBitmap Pointer to a bitmap +; Width ByRef variable. This variable will be set to the width of the bitmap +; Height ByRef variable. This variable will be set to the height of the bitmap +; +; return No return value +; Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height + +Gdip_GetImageDimensions(pBitmap, &Width, &Height) +{ + DllCall("gdiplus\GdipGetImageWidth", "UPtr", pBitmap, "uint*", &Width:=0) + DllCall("gdiplus\GdipGetImageHeight", "UPtr", pBitmap, "uint*", &Height:=0) +} + +;##################################################################################### + +Gdip_GetDimensions(pBitmap, &Width, &Height) +{ + Gdip_GetImageDimensions(pBitmap, &Width, &Height) +} + +;##################################################################################### + +Gdip_GetImagePixelFormat(pBitmap) +{ + DllCall("gdiplus\GdipGetImagePixelFormat", "UPtr", pBitmap, "UPtr*", &_Format:=0) + return _Format +} + +;##################################################################################### + +; Function Gdip_GetDpiX +; Description Gives the horizontal dots per inch of the graphics of a bitmap +; +; pBitmap Pointer to a bitmap +; Width ByRef variable. This variable will be set to the width of the bitmap +; Height ByRef variable. This variable will be set to the height of the bitmap +; +; return No return value +; Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height + +Gdip_GetDpiX(pGraphics) +{ + DllCall("gdiplus\GdipGetDpiX", "UPtr", pGraphics, "float*", &dpix:=0) + return Round(dpix) +} + +;##################################################################################### + +Gdip_GetDpiY(pGraphics) +{ + DllCall("gdiplus\GdipGetDpiY", "UPtr", pGraphics, "float*", &dpiy:=0) + return Round(dpiy) +} + +;##################################################################################### + +Gdip_GetImageHorizontalResolution(pBitmap) +{ + DllCall("gdiplus\GdipGetImageHorizontalResolution", "UPtr", pBitmap, "float*", &dpix:=0) + return Round(dpix) +} + +;##################################################################################### + +Gdip_GetImageVerticalResolution(pBitmap) +{ + DllCall("gdiplus\GdipGetImageVerticalResolution", "UPtr", pBitmap, "float*", &dpiy:=0) + return Round(dpiy) +} + +;##################################################################################### + +Gdip_BitmapSetResolution(pBitmap, dpix, dpiy) +{ + return DllCall("gdiplus\GdipBitmapSetResolution", "UPtr", pBitmap, "Float", dpix, "Float", dpiy) +} + +;##################################################################################### + +Gdip_CreateBitmapFromFile(sFile, IconNumber:=1, IconSize:="") +{ + SplitPath sFile,,, &extension:="" + if RegExMatch(extension, "^(?i:exe|dll)$") { + Sizes := IconSize ? IconSize : 256 "|" 128 "|" 64 "|" 48 "|" 32 "|" 16 + BufSize := 16 + (2*(A_PtrSize ? A_PtrSize : 4)) + + buf := Buffer(BufSize, 0) + hIcon := 0 + + for eachSize, Size in StrSplit( Sizes, "|" ) { + DllCall("PrivateExtractIcons", "str", sFile, "Int", IconNumber-1, "Int", Size, "Int", Size, "UPtr*", &hIcon, "UPtr*", 0, "UInt", 1, "UInt", 0) + + if (!hIcon) { + continue + } + + if !DllCall("GetIconInfo", "UPtr", hIcon, "UPtr", buf.Ptr) { + DestroyIcon(hIcon) + continue + } + + hbmMask := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4)) + hbmColor := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4) + (A_PtrSize ? A_PtrSize : 4)) + if !(hbmColor && DllCall("GetObject", "UPtr", hbmColor, "Int", BufSize, "UPtr", buf.Ptr)) + { + DestroyIcon(hIcon) + continue + } + break + } + + if (!hIcon) { + return -1 + } + + Width := NumGet(buf, 4, "Int"), Height := NumGet(buf, 8, "Int") + hbm := CreateDIBSection(Width, -Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm) + if !DllCall("DrawIconEx", "UPtr", hdc, "Int", 0, "Int", 0, "UPtr", hIcon, "UInt", Width, "UInt", Height, "UInt", 0, "UPtr", 0, "UInt", 3) { + DestroyIcon(hIcon) + return -2 + } + + dib := Buffer(104) + DllCall("GetObject", "UPtr", hbm, "Int", A_PtrSize = 8 ? 104 : 84, "UPtr", dib.Ptr) ; sizeof(DIBSECTION) = 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize + Stride := NumGet(dib, 12, "Int"), Bits := NumGet(dib, 20 + (A_PtrSize = 8 ? 4 : 0)) ; padding + DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", Width, "Int", Height, "Int", Stride, "Int", 0x26200A, "UPtr", Bits, "UPtr*", &pBitmapOld:=0) + pBitmap := Gdip_CreateBitmap(Width, Height) + _G := Gdip_GraphicsFromImage(pBitmap) + , Gdip_DrawImage(_G, pBitmapOld, 0, 0, Width, Height, 0, 0, Width, Height) + SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc) + Gdip_DeleteGraphics(_G), Gdip_DisposeImage(pBitmapOld) + DestroyIcon(hIcon) + + } else { + DllCall("gdiplus\GdipCreateBitmapFromFile", "UPtr", StrPtr(sFile), "UPtr*", &pBitmap:=0) + } + + return pBitmap +} + +;##################################################################################### + +Gdip_CreateBitmapFromHBITMAP(hBitmap, Palette:=0) +{ + DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "UPtr", hBitmap, "UPtr", Palette, "UPtr*", &pBitmap:=0) + return pBitmap +} + +;##################################################################################### + +Gdip_CreateHBITMAPFromBitmap(pBitmap, Background:=0xffffffff) +{ + DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "UPtr", pBitmap, "UPtr*", &hbm:=0, "Int", Background) + return hbm +} + +;##################################################################################### + +Gdip_CreateARGBBitmapFromHBITMAP(&hBitmap) { + ; struct BITMAP - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmap + dib := Buffer(76+2*(A_PtrSize=8?4:0)+2*A_PtrSize) + DllCall("GetObject" + , "ptr", hBitmap + , "Int", dib.Size + , "ptr", dib.Ptr) ; sizeof(DIBSECTION) = 84, 104 + , width := NumGet(dib, 4, "UInt") + , height := NumGet(dib, 8, "UInt") + , bpp := NumGet(dib, 18, "ushort") + + ; Fallback to built-in method if pixels are not 32-bit ARGB. + if (bpp != 32) { ; This built-in version is 120% faster but ignores transparency. + DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "ptr", hBitmap, "ptr", 0, "ptr*", &pBitmap:=0) + return pBitmap + } + + ; Create a handle to a device context and associate the image. + hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr") ; Creates a memory DC compatible with the current screen. + obm := DllCall("SelectObject", "ptr", hdc, "ptr", hBitmap, "ptr") ; Put the (hBitmap) image onto the device context. + + ; Create a device independent bitmap with negative height. All DIBs use the screen pixel format (pARGB). + ; Use hbm to buffer the image such that top-down and bottom-up images are mapped to this top-down buffer. + cdc := DllCall("CreateCompatibleDC", "ptr", hdc, "ptr") + bi := Buffer(40, 0) ; sizeof(bi) = 40 + NumPut( + "UInt", 40, ; Size + "UInt", width, ; Width + "Int", height, ; Height - Negative so (0, 0) is top-left. + "ushort", 1, ; Planes + "ushort", 32, ; BitCount / BitsPerPixel + bi) + hbm := DllCall("CreateDIBSection", "ptr", cdc, "ptr", bi.Ptr, "UInt", 0 + , "ptr*", &pBits:=0 ; pBits is the pointer to (top-down) pixel values. + , "ptr", 0, "UInt", 0, "ptr") + ob2 := DllCall("SelectObject", "ptr", cdc, "ptr", hbm, "ptr") + + ; This is the 32-bit ARGB pBitmap (different from an hBitmap) that will receive the final converted pixels. + DllCall("gdiplus\GdipCreateBitmapFromScan0" + , "Int", width, "Int", height, "Int", 0, "Int", 0x26200A, "ptr", 0, "ptr*", &pBitmap:=0) + + ; Create a Scan0 buffer pointing to pBits. The buffer has pixel format pARGB. + Rect := Buffer(16, 0) ; sizeof(Rect) = 16 + NumPut( + "UInt", width, ; Width + "UInt", height, ; Height + Rect, 8) + + BitmapData := Buffer(16+2*A_PtrSize, 0) ; sizeof(BitmapData) = 24, 32 + NumPut( + "UInt", width, ; Width + "UInt", height, ; Height + "Int", 4 * width, ; Stride + "Int", 0xE200B, ; PixelFormat + "ptr", pBits, ; Scan0 + BitmapData) + + ; Use LockBits to create a writable buffer that converts pARGB to ARGB. + DllCall("gdiplus\GdipBitmapLockBits" + , "ptr", pBitmap + , "ptr", Rect.Ptr + , "UInt", 6 ; ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly + , "Int", 0xE200B ; Format32bppPArgb + , "ptr", BitmapData.Ptr) ; Contains the pointer (pBits) to the hbm. + + ; Copies the image (hBitmap) to a top-down bitmap. Removes bottom-up-ness if present. + DllCall("gdi32\BitBlt" + , "ptr", cdc, "Int", 0, "Int", 0, "Int", width, "Int", height + , "ptr", hdc, "Int", 0, "Int", 0, "UInt", 0x00CC0020) ; SRCCOPY + + ; Convert the pARGB pixels copied into the device independent bitmap (hbm) to ARGB. + DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", BitmapData.Ptr) + + ; Cleanup the buffer and device contexts. + DllCall("SelectObject", "ptr", cdc, "ptr", ob2) + DllCall("DeleteObject", "ptr", hbm) + DllCall("DeleteDC", "ptr", cdc) + DllCall("SelectObject", "ptr", hdc, "ptr", obm) + DllCall("DeleteDC", "ptr", hdc) + + return pBitmap +} + +;##################################################################################### + +Gdip_CreateARGBHBITMAPFromBitmap(&pBitmap) { + ; This version is about 25% faster than Gdip_CreateHBITMAPFromBitmap(). + ; Get Bitmap width and height. + DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", &width:=0) + DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", &height:=0) + + ; Convert the source pBitmap into a hBitmap manually. + ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader + hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr") + bi := Buffer(40, 0) ; sizeof(bi) = 40 + NumPut( + "UInt", 40, ; Size + "UInt", width, ; Width + "Int", -height, ; Height - Negative so (0, 0) is top-left. + "ushort", 1, ; Planes + "ushort", 32, ; BitCount / BitsPerPixel + bi) + hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", bi.Ptr, "UInt", 0, "ptr*", &pBits:=0, "ptr", 0, "UInt", 0, "ptr") + obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr") + + ; Transfer data from source pBitmap to an hBitmap manually. + Rect := Buffer(16, 0) ; sizeof(Rect) = 16 + NumPut( + "UInt", width, ; Width + "UInt", height, ; Height + Rect, 8) + BitmapData := Buffer(16+2*A_PtrSize, 0) ; sizeof(BitmapData) = 24, 32 + NumPut( + "UInt", width, ; Width + "UInt", height, ; Height + "Int", 4 * width, ; Stride + "Int", 0xE200B, ; PixelFormat + "ptr", pBits, ; Scan0 + BitmapData) + DllCall("gdiplus\GdipBitmapLockBits" + , "ptr", pBitmap + , "ptr", Rect.Ptr + , "UInt", 5 ; ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly + , "Int", 0xE200B ; Format32bppPArgb + , "ptr", BitmapData.Ptr) ; Contains the pointer (pBits) to the hbm. + DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", BitmapData.Ptr) + + ; Cleanup the hBitmap and device contexts. + DllCall("SelectObject", "ptr", hdc, "ptr", obm) + DllCall("DeleteDC", "ptr", hdc) + + return hbm +} + +;##################################################################################### + +Gdip_CreateBitmapFromHICON(hIcon) +{ + DllCall("gdiplus\GdipCreateBitmapFromHICON", "UPtr", hIcon, "UPtr*", &pBitmap:=0) + return pBitmap +} + +;##################################################################################### + +Gdip_CreateHICONFromBitmap(pBitmap) +{ + DllCall("gdiplus\GdipCreateHICONFromBitmap", "UPtr", pBitmap, "UPtr*", &hIcon:=0) + return hIcon +} + +;##################################################################################### + +Gdip_CreateBitmap(Width, Height, Format:=0x26200A) +{ + DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", Width, "Int", Height, "Int", 0, "Int", Format, "UPtr", 0, "UPtr*", &pBitmap:=0) + return pBitmap +} + +;##################################################################################### + +Gdip_CreateBitmapFromClipboard() +{ + if !DllCall("IsClipboardFormatAvailable", "UInt", 8) { + return -2 + } + + if !DllCall("OpenClipboard", "UPtr", 0) { + return -1 + } + + hBitmap := DllCall("GetClipboardData", "UInt", 2, "UPtr") + + if !DllCall("CloseClipboard") { + return -5 + } + + if !hBitmap { + return -3 + } + + pBitmap := Gdip_CreateBitmapFromHBITMAP(hBitmap) + if (!pBitmap) { + return -4 + } + + DeleteObject(hBitmap) + + return pBitmap +} + +;##################################################################################### + +Gdip_SetBitmapToClipboard(pBitmap) +{ + off1 := A_PtrSize = 8 ? 52 : 44, off2 := A_PtrSize = 8 ? 32 : 24 + hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap) + oi := Buffer(A_PtrSize = 8 ? 104 : 84, 0) + DllCall("GetObject", "UPtr", hBitmap, "Int", oi.Size, "UPtr", oi.Ptr) + hdib := DllCall("GlobalAlloc", "UInt", 2, "UPtr", 40+NumGet(oi, off1, "UInt"), "UPtr") + pdib := DllCall("GlobalLock", "UPtr", hdib, "UPtr") + DllCall("RtlMoveMemory", "UPtr", pdib, "UPtr", oi.Ptr+off2, "UPtr", 40) + DllCall("RtlMoveMemory", "UPtr", pdib+40, "UPtr", NumGet(oi, off2 - (A_PtrSize ? A_PtrSize : 4), "UPtr"), "UPtr", NumGet(oi, off1, "UInt")) + DllCall("GlobalUnlock", "UPtr", hdib) + DllCall("DeleteObject", "UPtr", hBitmap) + DllCall("OpenClipboard", "UPtr", 0) + DllCall("EmptyClipboard") + DllCall("SetClipboardData", "UInt", 8, "UPtr", hdib) + DllCall("CloseClipboard") +} + +;##################################################################################### + +Gdip_CloneBitmapArea(pBitmap, x, y, w, h, Format:=0x26200A) +{ + DllCall("gdiplus\GdipCloneBitmapArea" + , "Float", x + , "Float", y + , "Float", w + , "Float", h + , "Int", Format + , "UPtr", pBitmap + , "UPtr*", &pBitmapDest:=0) + return pBitmapDest +} + +;##################################################################################### +; Create resources +;##################################################################################### + +Gdip_CreatePen(ARGB, w) +{ + DllCall("gdiplus\GdipCreatePen1", "UInt", ARGB, "Float", w, "Int", 2, "UPtr*", &pPen:=0) + return pPen +} + +;##################################################################################### + +Gdip_CreatePenFromBrush(pBrush, w) +{ + DllCall("gdiplus\GdipCreatePen2", "UPtr", pBrush, "Float", w, "Int", 2, "UPtr*", &pPen:=0) + return pPen +} + +;##################################################################################### + +Gdip_BrushCreateSolid(ARGB:=0xff000000) +{ + DllCall("gdiplus\GdipCreateSolidFill", "UInt", ARGB, "UPtr*", &pBrush:=0) + return pBrush +} + +;##################################################################################### + +; HatchStyleHorizontal = 0 +; HatchStyleVertical = 1 +; HatchStyleForwardDiagonal = 2 +; HatchStyleBackwardDiagonal = 3 +; HatchStyleCross = 4 +; HatchStyleDiagonalCross = 5 +; HatchStyle05Percent = 6 +; HatchStyle10Percent = 7 +; HatchStyle20Percent = 8 +; HatchStyle25Percent = 9 +; HatchStyle30Percent = 10 +; HatchStyle40Percent = 11 +; HatchStyle50Percent = 12 +; HatchStyle60Percent = 13 +; HatchStyle70Percent = 14 +; HatchStyle75Percent = 15 +; HatchStyle80Percent = 16 +; HatchStyle90Percent = 17 +; HatchStyleLightDownwardDiagonal = 18 +; HatchStyleLightUpwardDiagonal = 19 +; HatchStyleDarkDownwardDiagonal = 20 +; HatchStyleDarkUpwardDiagonal = 21 +; HatchStyleWideDownwardDiagonal = 22 +; HatchStyleWideUpwardDiagonal = 23 +; HatchStyleLightVertical = 24 +; HatchStyleLightHorizontal = 25 +; HatchStyleNarrowVertical = 26 +; HatchStyleNarrowHorizontal = 27 +; HatchStyleDarkVertical = 28 +; HatchStyleDarkHorizontal = 29 +; HatchStyleDashedDownwardDiagonal = 30 +; HatchStyleDashedUpwardDiagonal = 31 +; HatchStyleDashedHorizontal = 32 +; HatchStyleDashedVertical = 33 +; HatchStyleSmallConfetti = 34 +; HatchStyleLargeConfetti = 35 +; HatchStyleZigZag = 36 +; HatchStyleWave = 37 +; HatchStyleDiagonalBrick = 38 +; HatchStyleHorizontalBrick = 39 +; HatchStyleWeave = 40 +; HatchStylePlaid = 41 +; HatchStyleDivot = 42 +; HatchStyleDottedGrid = 43 +; HatchStyleDottedDiamond = 44 +; HatchStyleShingle = 45 +; HatchStyleTrellis = 46 +; HatchStyleSphere = 47 +; HatchStyleSmallGrid = 48 +; HatchStyleSmallCheckerBoard = 49 +; HatchStyleLargeCheckerBoard = 50 +; HatchStyleOutlinedDiamond = 51 +; HatchStyleSolidDiamond = 52 +; HatchStyleTotal = 53 +Gdip_BrushCreateHatch(ARGBfront, ARGBback, HatchStyle:=0) +{ + DllCall("gdiplus\GdipCreateHatchBrush", "Int", HatchStyle, "UInt", ARGBfront, "UInt", ARGBback, "UPtr*", &pBrush:=0) + return pBrush +} + +;##################################################################################### + +Gdip_CreateTextureBrush(pBitmap, WrapMode:=1, x:=0, y:=0, w:="", h:="") +{ + if !(w && h) { + DllCall("gdiplus\GdipCreateTexture", "UPtr", pBitmap, "Int", WrapMode, "UPtr*", &pBrush:=0) + } else { + DllCall("gdiplus\GdipCreateTexture2", "UPtr", pBitmap, "Int", WrapMode, "Float", x, "Float", y, "Float", w, "Float", h, "UPtr*", &pBrush:=0) + } + + return pBrush +} + +;##################################################################################### + +; WrapModeTile = 0 +; WrapModeTileFlipX = 1 +; WrapModeTileFlipY = 2 +; WrapModeTileFlipXY = 3 +; WrapModeClamp = 4 +Gdip_CreateLineBrush(x1, y1, x2, y2, ARGB1, ARGB2, WrapMode:=1) +{ + CreatePointF(&PointF1:="", x1, y1), CreatePointF(&PointF2:="", x2, y2) + DllCall("gdiplus\GdipCreateLineBrush", "UPtr", PointF1.Ptr, "UPtr", PointF2.Ptr, "UInt", ARGB1, "UInt", ARGB2, "Int", WrapMode, "UPtr*", &LGpBrush:=0) + return LGpBrush +} + +;##################################################################################### + +; LinearGradientModeHorizontal = 0 +; LinearGradientModeVertical = 1 +; LinearGradientModeForwardDiagonal = 2 +; LinearGradientModeBackwardDiagonal = 3 +Gdip_CreateLineBrushFromRect(x, y, w, h, ARGB1, ARGB2, LinearGradientMode:=1, WrapMode:=1) +{ + CreateRectF(&RectF:="", x, y, w, h) + DllCall("gdiplus\GdipCreateLineBrushFromRect", "UPtr", RectF.Ptr, "Int", ARGB1, "Int", ARGB2, "Int", LinearGradientMode, "Int", WrapMode, "UPtr*", &LGpBrush:=0) + return LGpBrush +} + +;##################################################################################### + +Gdip_CloneBrush(pBrush) +{ + DllCall("gdiplus\GdipCloneBrush", "UPtr", pBrush, "UPtr*", &pBrushClone:=0) + return pBrushClone +} + +;##################################################################################### +; Delete resources +;##################################################################################### + +Gdip_DeletePen(pPen) +{ + return DllCall("gdiplus\GdipDeletePen", "UPtr", pPen) +} + +;##################################################################################### + +Gdip_DeleteBrush(pBrush) +{ + return DllCall("gdiplus\GdipDeleteBrush", "UPtr", pBrush) +} + +;##################################################################################### + +Gdip_DisposeImage(pBitmap) +{ + return DllCall("gdiplus\GdipDisposeImage", "UPtr", pBitmap) +} + +;##################################################################################### + +Gdip_DeleteGraphics(pGraphics) +{ + return DllCall("gdiplus\GdipDeleteGraphics", "UPtr", pGraphics) +} + +;##################################################################################### + +Gdip_DisposeImageAttributes(ImageAttr) +{ + return DllCall("gdiplus\GdipDisposeImageAttributes", "UPtr", ImageAttr) +} + +;##################################################################################### + +Gdip_DeleteFont(hFont) +{ + return DllCall("gdiplus\GdipDeleteFont", "UPtr", hFont) +} + +;##################################################################################### + +Gdip_DeleteStringFormat(hFormat) +{ + return DllCall("gdiplus\GdipDeleteStringFormat", "UPtr", hFormat) +} + +;##################################################################################### + +Gdip_DeleteFontFamily(hFamily) +{ + return DllCall("gdiplus\GdipDeleteFontFamily", "UPtr", hFamily) +} + +;##################################################################################### + +Gdip_DeleteMatrix(Matrix) +{ + return DllCall("gdiplus\GdipDeleteMatrix", "UPtr", Matrix) +} + +;##################################################################################### +; Text functions +;##################################################################################### + +Gdip_TextToGraphics(pGraphics, Text, Options, Font:="Arial", Width:="", Height:="", Measure:=0) +{ + IWidth := Width + IHeight := Height + PassBrush := 0 + + + pattern_opts := "i)" + RegExMatch(Options, pattern_opts "X([\-\d\.]+)(p*)", &xpos:="") + RegExMatch(Options, pattern_opts "Y([\-\d\.]+)(p*)", &ypos:="") + RegExMatch(Options, pattern_opts "W([\-\d\.]+)(p*)", &Width:="") + RegExMatch(Options, pattern_opts "H([\-\d\.]+)(p*)", &Height:="") + RegExMatch(Options, pattern_opts "C(?!(entre|enter))([a-f\d]+)", &Colour:="") + RegExMatch(Options, pattern_opts "Top|Up|Bottom|Down|vCentre|vCenter", &vPos:="") + RegExMatch(Options, pattern_opts "NoWrap", &NoWrap:="") + RegExMatch(Options, pattern_opts "R(\d)", &Rendering:="") + RegExMatch(Options, pattern_opts "S(\d+)(p*)", &Size:="") + + if Colour && IsInteger(Colour[2]) && !Gdip_DeleteBrush(Gdip_CloneBrush(Colour[2])) { + PassBrush := 1, pBrush := Colour[2] + } + + if !(IWidth && IHeight) && ((xpos && xpos[2]) || (ypos && ypos[2]) || (Width && Width[2]) || (Height && Height[2]) || (Size && Size[2])) { + return -1 + } + + Style := 0 + Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout" + for eachStyle, valStyle in StrSplit( Styles, "|" ) { + if RegExMatch(Options, "\b" valStyle) + Style |= (valStyle != "StrikeOut") ? (A_Index-1) : 8 + } + + Align := 0 + Alignments := "Near|Left|Centre|Center|Far|Right" + for eachAlignment, valAlignment in StrSplit( Alignments, "|" ) { + if RegExMatch(Options, "\b" valAlignment) { + Align |= A_Index*10//21 ; 0|0|1|1|2|2 + } + } + + xpos := (xpos && (xpos[1] != "")) ? xpos[2] ? IWidth*(xpos[1]/100) : xpos[1] : 0 + ypos := (ypos && (ypos[1] != "")) ? ypos[2] ? IHeight*(ypos[1]/100) : ypos[1] : 0 + Width := (Width && Width[1]) ? Width[2] ? IWidth*(Width[1]/100) : Width[1] : IWidth + Height := (Height && Height[1]) ? Height[2] ? IHeight*(Height[1]/100) : Height[1] : IHeight + + if !PassBrush { + Colour := "0x" (Colour && Colour[2] ? Colour[2] : "ff000000") + } + + Rendering := (Rendering && (Rendering[1] >= 0) && (Rendering[1] <= 5)) ? Rendering[1] : 4 + Size := (Size && (Size[1] > 0)) ? Size[2] ? IHeight*(Size[1]/100) : Size[1] : 12 + + hFamily := Gdip_FontFamilyCreate(Font) + hFont := Gdip_FontCreate(hFamily, Size, Style) + FormatStyle := NoWrap ? 0x4000 | 0x1000 : 0x4000 + hFormat := Gdip_StringFormatCreate(FormatStyle) + pBrush := PassBrush ? pBrush : Gdip_BrushCreateSolid(Colour) + + if !(hFamily && hFont && hFormat && pBrush && pGraphics) { + return !pGraphics ? -2 : !hFamily ? -3 : !hFont ? -4 : !hFormat ? -5 : !pBrush ? -6 : 0 + } + + CreateRectF(&RC:="", xpos, ypos, Width, Height) + Gdip_SetStringFormatAlign(hFormat, Align) + Gdip_SetTextRenderingHint(pGraphics, Rendering) + ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, &RC) + + if vPos { + ReturnRC := StrSplit(ReturnRC, "|") + + if (vPos[0] = "vCentre") || (vPos[0] = "vCenter") + ypos += (Height-ReturnRC[4])//2 + else if (vPos[0] = "Top") || (vPos[0] = "Up") + ypos := 0 + else if (vPos[0] = "Bottom") || (vPos[0] = "Down") + ypos := Height-ReturnRC[4] + + CreateRectF(&RC, xpos, ypos, Width, ReturnRC[4]) + ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, &RC) + } + + if !Measure { + ReturnRC := Gdip_DrawString(pGraphics, Text, hFont, hFormat, pBrush, &RC) + } + + if !PassBrush { + Gdip_DeleteBrush(pBrush) + } + + Gdip_DeleteStringFormat(hFormat) + Gdip_DeleteFont(hFont) + Gdip_DeleteFontFamily(hFamily) + + return ReturnRC +} + +;##################################################################################### + +Gdip_DrawString(pGraphics, sString, hFont, hFormat, pBrush, &RectF) +{ + return DllCall("gdiplus\GdipDrawString" + , "UPtr", pGraphics + , "UPtr", StrPtr(sString) + , "Int", -1 + , "UPtr", hFont + , "UPtr", RectF.Ptr + , "UPtr", hFormat + , "UPtr", pBrush) +} + +;##################################################################################### + +Gdip_MeasureString(pGraphics, sString, hFont, hFormat, &RectF) +{ + RC := Buffer(16) + DllCall("gdiplus\GdipMeasureString" + , "UPtr", pGraphics + , "UPtr", StrPtr(sString) + , "Int", -1 + , "UPtr", hFont + , "UPtr", RectF.Ptr + , "UPtr", hFormat + , "UPtr", RC.Ptr + , "uint*", &Chars:=0 + , "uint*", &Lines:=0) + + return RC.Ptr ? NumGet(RC, 0, "Float") "|" NumGet(RC, 4, "Float") "|" NumGet(RC, 8, "Float") "|" NumGet(RC, 12, "Float") "|" Chars "|" Lines : 0 +} + +; Near = 0 +; Center = 1 +; Far = 2 +Gdip_SetStringFormatAlign(hFormat, Align) +{ + return DllCall("gdiplus\GdipSetStringFormatAlign", "UPtr", hFormat, "Int", Align) +} + +; StringFormatFlagsDirectionRightToLeft = 0x00000001 +; StringFormatFlagsDirectionVertical = 0x00000002 +; StringFormatFlagsNoFitBlackBox = 0x00000004 +; StringFormatFlagsDisplayFormatControl = 0x00000020 +; StringFormatFlagsNoFontFallback = 0x00000400 +; StringFormatFlagsMeasureTrailingSpaces = 0x00000800 +; StringFormatFlagsNoWrap = 0x00001000 +; StringFormatFlagsLineLimit = 0x00002000 +; StringFormatFlagsNoClip = 0x00004000 +Gdip_StringFormatCreate(Format:=0, Lang:=0) +{ + DllCall("gdiplus\GdipCreateStringFormat", "Int", Format, "Int", Lang, "UPtr*", &hFormat:=0) + return hFormat +} + +; Regular = 0 +; Bold = 1 +; Italic = 2 +; BoldItalic = 3 +; Underline = 4 +; Strikeout = 8 +Gdip_FontCreate(hFamily, Size, Style:=0) +{ + DllCall("gdiplus\GdipCreateFont", "UPtr", hFamily, "Float", Size, "Int", Style, "Int", 0, "UPtr*", &hFont:=0) + return hFont +} + +Gdip_FontFamilyCreate(Font) +{ + DllCall("gdiplus\GdipCreateFontFamilyFromName" + , "UPtr", StrPtr(Font) + , "UInt", 0 + , "UPtr*", &hFamily:=0) + + return hFamily +} + +;##################################################################################### +; Matrix functions +;##################################################################################### + +Gdip_CreateAffineMatrix(m11, m12, m21, m22, x, y) +{ + DllCall("gdiplus\GdipCreateMatrix2", "Float", m11, "Float", m12, "Float", m21, "Float", m22, "Float", x, "Float", y, "UPtr*", &Matrix:=0) + return Matrix +} + +Gdip_CreateMatrix() +{ + DllCall("gdiplus\GdipCreateMatrix", "UPtr*", &Matrix:=0) + return Matrix +} + +;##################################################################################### +; GraphicsPath functions +;##################################################################################### + +; Alternate = 0 +; Winding = 1 +Gdip_CreatePath(BrushMode:=0) +{ + DllCall("gdiplus\GdipCreatePath", "Int", BrushMode, "UPtr*", &pPath:=0) + return pPath +} + +Gdip_AddPathEllipse(pPath, x, y, w, h) +{ + return DllCall("gdiplus\GdipAddPathEllipse", "UPtr", pPath, "Float", x, "Float", y, "Float", w, "Float", h) +} + +Gdip_AddPathPolygon(pPath, Points) +{ + Points := StrSplit(Points, "|") + PointsLength := Points.Length + PointF := Buffer(8*PointsLength) + for eachPoint, Point in Points + { + Coord := StrSplit(Point, ",") + NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) + NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) + } + + return DllCall("gdiplus\GdipAddPathPolygon", "UPtr", pPath, "UPtr", PointF.Ptr, "Int", PointsLength) +} + +Gdip_DeletePath(pPath) +{ + return DllCall("gdiplus\GdipDeletePath", "UPtr", pPath) +} + +;##################################################################################### +; Quality functions +;##################################################################################### + +; SystemDefault = 0 +; SingleBitPerPixelGridFit = 1 +; SingleBitPerPixel = 2 +; AntiAliasGridFit = 3 +; AntiAlias = 4 +Gdip_SetTextRenderingHint(pGraphics, RenderingHint) +{ + return DllCall("gdiplus\GdipSetTextRenderingHint", "UPtr", pGraphics, "Int", RenderingHint) +} + +; Default = 0 +; LowQuality = 1 +; HighQuality = 2 +; Bilinear = 3 +; Bicubic = 4 +; NearestNeighbor = 5 +; HighQualityBilinear = 6 +; HighQualityBicubic = 7 +Gdip_SetInterpolationMode(pGraphics, InterpolationMode) +{ + return DllCall("gdiplus\GdipSetInterpolationMode", "UPtr", pGraphics, "Int", InterpolationMode) +} + +; Default = 0 +; HighSpeed = 1 +; HighQuality = 2 +; None = 3 +; AntiAlias = 4 +Gdip_SetSmoothingMode(pGraphics, SmoothingMode) +{ + return DllCall("gdiplus\GdipSetSmoothingMode", "UPtr", pGraphics, "Int", SmoothingMode) +} + +; CompositingModeSourceOver = 0 (blended) +; CompositingModeSourceCopy = 1 (overwrite) +Gdip_SetCompositingMode(pGraphics, CompositingMode:=0) +{ + return DllCall("gdiplus\GdipSetCompositingMode", "UPtr", pGraphics, "Int", CompositingMode) +} + +;##################################################################################### +; Extra functions +;##################################################################################### + +Gdip_Startup() +{ + if (!DllCall("LoadLibrary", "str", "gdiplus", "UPtr")) { + throw Error("Could not load GDI+ library") + } + + si := Buffer(A_PtrSize = 4 ? 20:32, 0) ; sizeof(GdiplusStartupInputEx) = 20, 32 + NumPut("uint", 0x2, si) + NumPut("uint", 0x4, si, A_PtrSize = 4 ? 16:24) + DllCall("gdiplus\GdiplusStartup", "UPtr*", &pToken:=0, "Ptr", si, "UPtr", 0) + if (!pToken) { + throw Error("Gdiplus failed to start. Please ensure you have gdiplus on your system") + } + + return pToken +} + +Gdip_Shutdown(pToken) +{ + DllCall("gdiplus\GdiplusShutdown", "UPtr", pToken) + hModule := DllCall("GetModuleHandle", "str", "gdiplus", "UPtr") + if (!hModule) { + throw Error("GDI+ library was unloaded before shutdown") + } + if (!DllCall("FreeLibrary", "UPtr", hModule)) { + throw Error("Could not free GDI+ library") + } + + return 0 +} + +; Prepend = 0; The new operation is applied before the old operation. +; Append = 1; The new operation is applied after the old operation. +Gdip_RotateWorldTransform(pGraphics, Angle, MatrixOrder:=0) +{ + return DllCall("gdiplus\GdipRotateWorldTransform", "UPtr", pGraphics, "Float", Angle, "Int", MatrixOrder) +} + +Gdip_ScaleWorldTransform(pGraphics, x, y, MatrixOrder:=0) +{ + return DllCall("gdiplus\GdipScaleWorldTransform", "UPtr", pGraphics, "Float", x, "Float", y, "Int", MatrixOrder) +} + +Gdip_TranslateWorldTransform(pGraphics, x, y, MatrixOrder:=0) +{ + return DllCall("gdiplus\GdipTranslateWorldTransform", "UPtr", pGraphics, "Float", x, "Float", y, "Int", MatrixOrder) +} + +Gdip_ResetWorldTransform(pGraphics) +{ + return DllCall("gdiplus\GdipResetWorldTransform", "UPtr", pGraphics) +} + +Gdip_GetRotatedTranslation(Width, Height, Angle, &xTranslation, &yTranslation) +{ + pi := 3.14159, TAngle := Angle*(pi/180) + + Bound := (Angle >= 0) ? Mod(Angle, 360) : 360-Mod(-Angle, -360) + if ((Bound >= 0) && (Bound <= 90)) { + xTranslation := Height*Sin(TAngle), yTranslation := 0 + } else if ((Bound > 90) && (Bound <= 180)) { + xTranslation := (Height*Sin(TAngle))-(Width*Cos(TAngle)), yTranslation := -Height*Cos(TAngle) + } else if ((Bound > 180) && (Bound <= 270)) { + xTranslation := -(Width*Cos(TAngle)), yTranslation := -(Height*Cos(TAngle))-(Width*Sin(TAngle)) + } else if ((Bound > 270) && (Bound <= 360)) { + xTranslation := 0, yTranslation := -Width*Sin(TAngle) + } +} + +Gdip_GetRotatedDimensions(Width, Height, Angle, &RWidth, &RHeight) +{ + pi := 3.14159, TAngle := Angle*(pi/180) + + if !(Width && Height) { + return -1 + } + + RWidth := Ceil(Abs(Width*Cos(TAngle))+Abs(Height*Sin(TAngle))) + RHeight := Ceil(Abs(Width*Sin(TAngle))+Abs(Height*Cos(Tangle))) +} + +; RotateNoneFlipNone = 0 +; Rotate90FlipNone = 1 +; Rotate180FlipNone = 2 +; Rotate270FlipNone = 3 +; RotateNoneFlipX = 4 +; Rotate90FlipX = 5 +; Rotate180FlipX = 6 +; Rotate270FlipX = 7 +; RotateNoneFlipY = Rotate180FlipX +; Rotate90FlipY = Rotate270FlipX +; Rotate180FlipY = RotateNoneFlipX +; Rotate270FlipY = Rotate90FlipX +; RotateNoneFlipXY = Rotate180FlipNone +; Rotate90FlipXY = Rotate270FlipNone +; Rotate180FlipXY = RotateNoneFlipNone +; Rotate270FlipXY = Rotate90FlipNone + +Gdip_ImageRotateFlip(pBitmap, RotateFlipType:=1) +{ + return DllCall("gdiplus\GdipImageRotateFlip", "UPtr", pBitmap, "Int", RotateFlipType) +} + +; Replace = 0 +; Intersect = 1 +; Union = 2 +; Xor = 3 +; Exclude = 4 +; Complement = 5 +Gdip_SetClipRect(pGraphics, x, y, w, h, CombineMode:=0) +{ + return DllCall("gdiplus\GdipSetClipRect", "UPtr", pGraphics, "Float", x, "Float", y, "Float", w, "Float", h, "Int", CombineMode) +} + +Gdip_SetClipPath(pGraphics, pPath, CombineMode:=0) +{ + return DllCall("gdiplus\GdipSetClipPath", "UPtr", pGraphics, "UPtr", pPath, "Int", CombineMode) +} + +Gdip_ResetClip(pGraphics) +{ + return DllCall("gdiplus\GdipResetClip", "UPtr", pGraphics) +} + +Gdip_GetClipRegion(pGraphics) +{ + Region := Gdip_CreateRegion() + DllCall("gdiplus\GdipGetClip", "UPtr", pGraphics, "UPtr", Region) + return Region +} + +Gdip_SetClipRegion(pGraphics, Region, CombineMode:=0) +{ + return DllCall("gdiplus\GdipSetClipRegion", "UPtr", pGraphics, "UPtr", Region, "Int", CombineMode) +} + +Gdip_CreateRegion() +{ + DllCall("gdiplus\GdipCreateRegion", "UPtr*", &Region:=0) + return Region +} + +Gdip_DeleteRegion(Region) +{ + return DllCall("gdiplus\GdipDeleteRegion", "UPtr", Region) +} + +;##################################################################################### +; BitmapLockBits +;##################################################################################### + +Gdip_LockBits(pBitmap, x, y, w, h, &Stride, &Scan0, &BitmapData, LockMode := 3, PixelFormat := 0x26200a) +{ + CreateRect(&_Rect:="", x, y, w, h) + BitmapData := Buffer(16+2*(A_PtrSize ? A_PtrSize : 4), 0) + _E := DllCall("Gdiplus\GdipBitmapLockBits", "UPtr", pBitmap, "UPtr", _Rect.Ptr, "UInt", LockMode, "Int", PixelFormat, "UPtr", BitmapData.Ptr) + Stride := NumGet(BitmapData, 8, "Int") + Scan0 := NumGet(BitmapData, 16, "UPtr") + return _E +} + +;##################################################################################### + +Gdip_UnlockBits(pBitmap, &BitmapData) +{ + return DllCall("Gdiplus\GdipBitmapUnlockBits", "UPtr", pBitmap, "UPtr", BitmapData.Ptr) +} + +;##################################################################################### + +Gdip_SetLockBitPixel(ARGB, Scan0, x, y, Stride) +{ + Numput("UInt", ARGB, Scan0+0, (x*4)+(y*Stride)) +} + +;##################################################################################### + +Gdip_GetLockBitPixel(Scan0, x, y, Stride) +{ + return NumGet(Scan0+0, (x*4)+(y*Stride), "UInt") +} + +;##################################################################################### + +Gdip_PixelateBitmap(pBitmap, &pBitmapOut, BlockSize) +{ + static PixelateBitmap := "" + + if (!PixelateBitmap) + { + if A_PtrSize != 8 ; x86 machine code + MCode_PixelateBitmap := " + (LTrim Join + 558BEC83EC3C8B4514538B5D1C99F7FB56578BC88955EC894DD885C90F8E830200008B451099F7FB8365DC008365E000894DC88955F08945E833FF897DD4 + 397DE80F8E160100008BCB0FAFCB894DCC33C08945F88945FC89451C8945143BD87E608B45088D50028BC82BCA8BF02BF2418945F48B45E02955F4894DC4 + 8D0CB80FAFCB03CA895DD08BD1895DE40FB64416030145140FB60201451C8B45C40FB604100145FC8B45F40FB604020145F883C204FF4DE475D6034D18FF + 4DD075C98B4DCC8B451499F7F98945148B451C99F7F989451C8B45FC99F7F98945FC8B45F899F7F98945F885DB7E648B450C8D50028BC82BCA83C103894D + C48BC82BCA41894DF48B4DD48945E48B45E02955E48D0C880FAFCB03CA895DD08BD18BF38A45148B7DC48804178A451C8B7DF488028A45FC8804178A45F8 + 8B7DE488043A83C2044E75DA034D18FF4DD075CE8B4DCC8B7DD447897DD43B7DE80F8CF2FEFFFF837DF0000F842C01000033C08945F88945FC89451C8945 + 148945E43BD87E65837DF0007E578B4DDC034DE48B75E80FAF4D180FAFF38B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945CC0F + B6440E030145140FB60101451C0FB6440F010145FC8B45F40FB604010145F883C104FF4DCC75D8FF45E4395DE47C9B8B4DF00FAFCB85C9740B8B451499F7 + F9894514EB048365140033F63BCE740B8B451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB + 038975F88975E43BDE7E5A837DF0007E4C8B4DDC034DE48B75E80FAF4D180FAFF38B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955CC8A55 + 1488540E038A551C88118A55FC88540F018A55F888140183C104FF4DCC75DFFF45E4395DE47CA68B45180145E0015DDCFF4DC80F8594FDFFFF8B451099F7 + FB8955F08945E885C00F8E450100008B45EC0FAFC38365DC008945D48B45E88945CC33C08945F88945FC89451C8945148945103945EC7E6085DB7E518B4D + D88B45080FAFCB034D108D50020FAF4D18034DDC8BF08BF88945F403CA2BF22BFA2955F4895DC80FB6440E030145140FB60101451C0FB6440F010145FC8B + 45F40FB604080145F883C104FF4DC875D8FF45108B45103B45EC7CA08B4DD485C9740B8B451499F7F9894514EB048365140033F63BCE740B8B451C99F7F9 + 89451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975103975EC7E5585DB7E468B4DD88B450C + 0FAFCB034D108D50020FAF4D18034DDC8BF08BF803CA2BF22BFA2BC2895DC88A551488540E038A551C88118A55FC88540F018A55F888140183C104FF4DC8 + 75DFFF45108B45103B45EC7CAB8BC3C1E0020145DCFF4DCC0F85CEFEFFFF8B4DEC33C08945F88945FC89451C8945148945103BC87E6C3945F07E5C8B4DD8 + 8B75E80FAFCB034D100FAFF30FAF4D188B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945C80FB6440E030145140FB60101451C0F + B6440F010145FC8B45F40FB604010145F883C104FF4DC875D833C0FF45108B4DEC394D107C940FAF4DF03BC874068B451499F7F933F68945143BCE740B8B + 451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975083975EC7E63EB0233F639 + 75F07E4F8B4DD88B75E80FAFCB034D080FAFF30FAF4D188B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955108A551488540E038A551C8811 + 8A55FC88540F018A55F888140883C104FF4D1075DFFF45088B45083B45EC7C9F5F5E33C05BC9C21800 + )" + else ; x64 machine code + MCode_PixelateBitmap := " + (LTrim Join + 4489442418488954241048894C24085355565741544155415641574883EC28418BC1448B8C24980000004C8BDA99488BD941F7F9448BD0448BFA8954240C + 448994248800000085C00F8E9D020000418BC04533E4458BF299448924244C8954241041F7F933C9898C24980000008BEA89542404448BE889442408EB05 + 4C8B5C24784585ED0F8E1A010000458BF1418BFD48897C2418450FAFF14533D233F633ED4533E44533ED4585C97E5B4C63BC2490000000418D040A410FAF + C148984C8D441802498BD9498BD04D8BD90FB642010FB64AFF4403E80FB60203E90FB64AFE4883C2044403E003F149FFCB75DE4D03C748FFCB75D0488B7C + 24188B8C24980000004C8B5C2478418BC59941F7FE448BE8418BC49941F7FE448BE08BC59941F7FE8BE88BC69941F7FE8BF04585C97E4048639C24900000 + 004103CA4D8BC1410FAFC94863C94A8D541902488BCA498BC144886901448821408869FF408871FE4883C10448FFC875E84803D349FFC875DA8B8C249800 + 0000488B5C24704C8B5C24784183C20448FFCF48897C24180F850AFFFFFF8B6C2404448B2424448B6C24084C8B74241085ED0F840A01000033FF33DB4533 + DB4533D24533C04585C97E53488B74247085ED7E42438D0C04418BC50FAF8C2490000000410FAFC18D04814863C8488D5431028BCD0FB642014403D00FB6 + 024883C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC17CB28BCD410FAFC985C9740A418BC299F7F98BF0EB0233F685C9740B418BC3 + 99F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585C97E4D4C8B74247885ED7E3841 + 8D0C14418BC50FAF8C2490000000410FAFC18D04814863C84A8D4431028BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2413BD17CBD + 4C8B7424108B8C2498000000038C2490000000488B5C24704503E149FFCE44892424898C24980000004C897424100F859EFDFFFF448B7C240C448B842480 + 000000418BC09941F7F98BE8448BEA89942498000000896C240C85C00F8E3B010000448BAC2488000000418BCF448BF5410FAFC9898C248000000033FF33 + ED33F64533DB4533D24533C04585FF7E524585C97E40418BC5410FAFC14103C00FAF84249000000003C74898488D541802498BD90FB642014403D00FB602 + 4883C2044403D80FB642FB03F00FB642FA03E848FFCB75DE488B5C247041FFC0453BC77CAE85C9740B418BC299F7F9448BE0EB034533E485C9740A418BC3 + 99F7F98BD8EB0233DB85C9740A8BC699F7F9448BD8EB034533DB85C9740A8BC599F7F9448BD0EB034533D24533C04585FF7E4E488B4C24784585C97E3541 + 8BC5410FAFC14103C00FAF84249000000003C74898488D540802498BC144886201881A44885AFF448852FE4883C20448FFC875E941FFC0453BC77CBE8B8C + 2480000000488B5C2470418BC1C1E00203F849FFCE0F85ECFEFFFF448BAC24980000008B6C240C448BA4248800000033FF33DB4533DB4533D24533C04585 + FF7E5A488B7424704585ED7E48418BCC8BC5410FAFC94103C80FAF8C2490000000410FAFC18D04814863C8488D543102418BCD0FB642014403D00FB60248 + 83C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC77CAB418BCF410FAFCD85C9740A418BC299F7F98BF0EB0233F685C9740B418BC399 + F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585FF7E4E4585ED7E42418BCC8BC541 + 0FAFC903CA0FAF8C2490000000410FAFC18D04814863C8488B442478488D440102418BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2 + 413BD77CB233C04883C428415F415E415D415C5F5E5D5BC3 + )" + + PixelateBitmap := Buffer(StrLen(MCode_PixelateBitmap)//2) + nCount := StrLen(MCode_PixelateBitmap)//2 + loop nCount { + NumPut("UChar", "0x" SubStr(MCode_PixelateBitmap, (2*A_Index)-1, 2), PixelateBitmap, A_Index-1) + } + DllCall("VirtualProtect", "UPtr", PixelateBitmap.Ptr, "UPtr", PixelateBitmap.Size, "UInt", 0x40, "UPtr*", 0) + } + + Gdip_GetImageDimensions(pBitmap, &Width:="", &Height:="") + + if (Width != Gdip_GetImageWidth(pBitmapOut) || Height != Gdip_GetImageHeight(pBitmapOut)) + return -1 + if (BlockSize > Width || BlockSize > Height) + return -2 + + E1 := Gdip_LockBits(pBitmap, 0, 0, Width, Height, &Stride1:="", &Scan01:="", &BitmapData1:="") + E2 := Gdip_LockBits(pBitmapOut, 0, 0, Width, Height, &Stride2:="", &Scan02:="", &BitmapData2:="") + if (E1 || E2) + return -3 + + ; E := - unused exit code + DllCall(PixelateBitmap.Ptr, "UPtr", Scan01, "UPtr", Scan02, "Int", Width, "Int", Height, "Int", Stride1, "Int", BlockSize) + + Gdip_UnlockBits(pBitmap, &BitmapData1), Gdip_UnlockBits(pBitmapOut, &BitmapData2) + + return 0 +} + +;##################################################################################### + +Gdip_ToARGB(A, R, G, B) +{ + return (A << 24) | (R << 16) | (G << 8) | B +} + +;##################################################################################### + +Gdip_FromARGB(ARGB, &A, &R, &G, &B) +{ + A := (0xff000000 & ARGB) >> 24 + R := (0x00ff0000 & ARGB) >> 16 + G := (0x0000ff00 & ARGB) >> 8 + B := 0x000000ff & ARGB +} + +;##################################################################################### + +Gdip_AFromARGB(ARGB) +{ + return (0xff000000 & ARGB) >> 24 +} + +;##################################################################################### + +Gdip_RFromARGB(ARGB) +{ + return (0x00ff0000 & ARGB) >> 16 +} + +;##################################################################################### + +Gdip_GFromARGB(ARGB) +{ + return (0x0000ff00 & ARGB) >> 8 +} + +;##################################################################################### + +Gdip_BFromARGB(ARGB) +{ + return 0x000000ff & ARGB +} + +;##################################################################################### + +StrGetB(Address, Length:=-1, Encoding:=0) +{ + ; Flexible parameter handling: + if !IsInteger(Length) { + Encoding := Length, Length := -1 + } + + ; Check for obvious errors. + if (Address+0 < 1024) { + return + } + + ; Ensure 'Encoding' contains a numeric identifier. + if (Encoding = "UTF-16") { + Encoding := 1200 + } else if (Encoding = "UTF-8") { + Encoding := 65001 + } else if SubStr(Encoding,1,2)="CP" { + Encoding := SubStr(Encoding,3) + } + + if !Encoding { ; "" or 0 + ; No conversion necessary, but we might not want the whole string. + if (Length == -1) + Length := DllCall("lstrlen", "Ptr", Address) + VarSetStrCapacity(&myString, Length) + DllCall("lstrcpyn", "str", myString, "Ptr", Address, "Int", Length + 1) + + } else if (Encoding = 1200) { ; UTF-16 + char_count := DllCall("WideCharToMultiByte", "UInt", 0, "UInt", 0x400, "Ptr", Address, "Int", Length, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0) + VarSetStrCapacity(&myString, char_count) + DllCall("WideCharToMultiByte", "UInt", 0, "UInt", 0x400, "Ptr", Address, "Int", Length, "str", myString, "Int", char_count, "UInt", 0, "UInt", 0) + + } else if IsInteger(Encoding) { + ; Convert from target encoding to UTF-16 then to the active code page. + char_count := DllCall("MultiByteToWideChar", "UInt", Encoding, "UInt", 0, "Ptr", Address, "Int", Length, "Ptr", 0, "Int", 0) + VarSetStrCapacity(&myString, char_count * 2) + char_count := DllCall("MultiByteToWideChar", "UInt", Encoding, "UInt", 0, "Ptr", Address, "Int", Length, "Ptr", myString.Ptr, "Int", char_count * 2) + myString := StrGetB(myString.Ptr, char_count, 1200) + } + + return myString +} + + +; ====================================================================================================================== +; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx +; by 'just me' +; https://autohotkey.com/boards/viewtopic.php?f=6&t=4606 +; ====================================================================================================================== +GetMonitorCount() +{ + Monitors := MDMF_Enum() + for k,v in Monitors { + count := A_Index + } + return count +} + +GetMonitorInfo(MonitorNum) +{ + Monitors := MDMF_Enum() + for k,v in Monitors { + if (v.Num = MonitorNum) { + return v + } + } +} + +GetPrimaryMonitor() +{ + Monitors := MDMF_Enum() + for k,v in Monitors { + if (v.Primary) { + return v.Num + } + } +} +; ---------------------------------------------------------------------------------------------------------------------- +; Name ..........: MDMF - Multiple Display Monitor Functions +; Description ...: Various functions for multiple display monitor environments +; Tested with ...: AHK 1.1.32.00 (A32/U32/U64) and 2.0-a108-a2fa0498 (U32/U64) +; Original Author: just me (https://www.autohotkey.com/boards/viewtopic.php?f=6&t=4606) +; Mod Authors ...: iPhilip, guest3456 +; Changes .......: Modified to work with v2.0-a108 and changed 'Count' key to 'TotalCount' to avoid conflicts +; ................ Modified MDMF_Enum() so that it works under both AHK v1 and v2. +; ................ Modified MDMF_EnumProc() to provide Count and Primary keys to the Monitors array. +; ................ Modified MDMF_FromHWND() to allow flag values that determine the function's return value if the +; ................ window does not intersect any display monitor. +; ................ Modified MDMF_FromPoint() to allow the cursor position to be returned ByRef if not specified and +; ................ allow flag values that determine the function's return value if the point is not contained within +; ................ any display monitor. +; ................ Modified MDMF_FromRect() to allow flag values that determine the function's return value if the +; ................ rectangle does not intersect any display monitor. +;................. Modified MDMF_GetInfo() with minor changes. +; ---------------------------------------------------------------------------------------------------------------------- +; +; ====================================================================================================================== +; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx ======================= +; ====================================================================================================================== +; Enumerates display monitors and returns an object containing the properties of all monitors or the specified monitor. +; ====================================================================================================================== +MDMF_Enum(HMON := "") { + static EnumProc := CallbackCreate(MDMF_EnumProc) + static Monitors := Map() + + if (HMON = "") { ; new enumeration + Monitors := Map("TotalCount", 0) + if !DllCall("User32.dll\EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", EnumProc, "Ptr", ObjPtr(Monitors), "Int") + return False + } + + return (HMON = "") ? Monitors : Monitors.HasKey(HMON) ? Monitors[HMON] : False +} +; ====================================================================================================================== +; Callback function that is called by the MDMF_Enum function. +; ====================================================================================================================== +MDMF_EnumProc(HMON, HDC, PRECT, ObjectAddr) { + Monitors := ObjFromPtrAddRef(ObjectAddr) + + Monitors[HMON] := MDMF_GetInfo(HMON) + Monitors["TotalCount"]++ + if (Monitors[HMON].Primary) { + Monitors["Primary"] := HMON + } + + return true +} +; ====================================================================================================================== +; Retrieves the display monitor that has the largest area of intersection with a specified window. +; The following flag values determine the function's return value if the window does not intersect any display monitor: +; MONITOR_DEFAULTTONULL = 0 - Returns NULL. +; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. +; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window. +; ====================================================================================================================== +MDMF_FromHWND(HWND, Flag := 0) { + return DllCall("User32.dll\MonitorFromWindow", "Ptr", HWND, "UInt", Flag, "Ptr") +} +; ====================================================================================================================== +; Retrieves the display monitor that contains a specified point. +; If either X or Y is empty, the function will use the current cursor position for this value and return it ByRef. +; The following flag values determine the function's return value if the point is not contained within any +; display monitor: +; MONITOR_DEFAULTTONULL = 0 - Returns NULL. +; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. +; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the point. +; ====================================================================================================================== +MDMF_FromPoint(&X:="", &Y:="", Flag:=0) { + if (X = "") || (Y = "") { + PT := Buffer(8, 0) + DllCall("User32.dll\GetCursorPos", "Ptr", PT.Ptr, "Int") + + if (X = "") { + X := NumGet(PT, 0, "Int") + } + + if (Y = "") { + Y := NumGet(PT, 4, "Int") + } + } + return DllCall("User32.dll\MonitorFromPoint", "Int64", (X & 0xFFFFFFFF) | (Y << 32), "UInt", Flag, "Ptr") +} +; ====================================================================================================================== +; Retrieves the display monitor that has the largest area of intersection with a specified rectangle. +; Parameters are consistent with the common AHK definition of a rectangle, which is X, Y, W, H instead of +; Left, Top, Right, Bottom. +; The following flag values determine the function's return value if the rectangle does not intersect any +; display monitor: +; MONITOR_DEFAULTTONULL = 0 - Returns NULL. +; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. +; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the rectangle. +; ====================================================================================================================== +MDMF_FromRect(X, Y, W, H, Flag := 0) { + RC := Buffer(16, 0) + NumPut("Int", X, "Int", Y, "Int", X + W, "Int", Y + H, RC) + return DllCall("User32.dll\MonitorFromRect", "Ptr", RC.Ptr, "UInt", Flag, "Ptr") +} +; ====================================================================================================================== +; Retrieves information about a display monitor. +; ====================================================================================================================== +MDMF_GetInfo(HMON) { + MIEX := Buffer(40 + (32 << !!1)) + NumPut("UInt", MIEX.Size, MIEX) + if DllCall("User32.dll\GetMonitorInfo", "Ptr", HMON, "Ptr", MIEX.Ptr, "Int") { + return {Name: (Name := StrGet(MIEX.Ptr + 40, 32)) ; CCHDEVICENAME = 32 + , Num: RegExReplace(Name, ".*(\d+)$", "$1") + , Left: NumGet(MIEX, 4, "Int") ; display rectangle + , Top: NumGet(MIEX, 8, "Int") ; " + , Right: NumGet(MIEX, 12, "Int") ; " + , Bottom: NumGet(MIEX, 16, "Int") ; " + , WALeft: NumGet(MIEX, 20, "Int") ; work area + , WATop: NumGet(MIEX, 24, "Int") ; " + , WARight: NumGet(MIEX, 28, "Int") ; " + , WABottom: NumGet(MIEX, 32, "Int") ; " + , Primary: NumGet(MIEX, 36, "UInt")} ; contains a non-zero value for the primary monitor. + } + return False +} + + +; Based on WinGetClientPos by dd900 and Frosti - https://www.autohotkey.com/boards/viewtopic.php?t=484 +WinGetRect( hwnd, &x:="", &y:="", &w:="", &h:="" ) { + Ptr := A_PtrSize ? "UPtr" : "UInt" + CreateRect(&winRect, 0, 0, 0, 0) ;is 16 on both 32 and 64 + ;VarSetCapacity( winRect, 16, 0 ) ; Alternative of above two lines + DllCall( "GetWindowRect", "Ptr", hwnd, "Ptr", winRect ) + x := NumGet(winRect, 0, "UInt") + y := NumGet(winRect, 4, "UInt") + w := NumGet(winRect, 8, "UInt") - x + h := NumGet(winRect, 12, "UInt") - y +} diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 7cb6c99..856e86b 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -14,9 +14,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * - */ +*/ #Include +#Include +#Include global LVM_GETCOLUMNWIDTH := 0x101D ; https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles @@ -24,6 +26,319 @@ global WS_EX_NOACTIVATE := "+E0x8000000" global WS_EX_COMPOSITED := "+E0x02000000" global WS_EX_LAYERED := "+E0x00080000" +class CandidateBox { + pToken := 0 + gui := 0 + hDC := 0 + pBitmap := 0 + hBitmap := 0 + oBitmap := 0 + pGraphics := 0 + hFont := 0 + hFormat := 0 + + static isHidden := 1 + + __New() { + if !this.pToken { + this.pToken := Gdip_Startup() + if !this.pToken { + MsgBox("GDI+ failed to start.") + ExitApp + } + } + ; +E0x8080088: WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST + this.gui := Gui("-Caption +E0x8080088 +LastFound -DPIScale +AlwaysOnTop", "CandidateBox") + this.dpiSacle := GUIUtilities.GetMonitorDpiScale() + + this.UpdateUIStyle() + } + + __Delete() { + this.ReleaseAll() + } + + UpdateUIStyle() { + this.borderWidth := UIStyle.border_width + this.borderColor := UIStyle.border_color + this.boxCornerR := UIStyle.corner_radius + this.hlCornerR := UIStyle.round_corner + this.lineSpacing := UIStyle.margin_y + this.padding := UIStyle.margin_x + + ; only use one font to show + this.fontName := UIStyle.font_face + this.fontSize := UIStyle.font_point + + ; preedite style + this.textColor := UIStyle.text_color + this.backgroundColor := UIStyle.back_color + this.hlTxtColor := UIStyle.hilited_text_color + this.hlBgColor := UIStyle.hilited_back_color + ; candidate style + this.hlCandTxtColor := UIStyle.hilited_candidate_text_color + this.hlCandBgColor := UIStyle.hilited_candidate_back_color + this.candTxtColor := UIStyle.candidate_text_color + this.candBgColor := UIStyle.candidate_back_color + + ; some color schemes have no these colors + this.labelColor := UIStyle.label_color + this.hlLabelColor := UIStyle.hilited_label_color + this.commentTxtColor := UIStyle.comment_text_color + this.hlCommentTxtColor := UIStyle.hilited_comment_text_color + } + + Build(context, &calcW, &calcH) { + local menu := context.menu + local cands := menu.candidates + this.num_candidates := menu.num_candidates + this.hilited_index := menu.highlighted_candidate_index + 1 + + GetCompositionText(context.composition, &pre_selected, &selected, &post_selected) + this.prdSelTxt := pre_selected + this.prdHlSelTxt := selected + this.prdHlUnselTxt := post_selected + this.labelsInfoArray := [] + this.candsInfoArray := [] + this.commentsInfoArray := [] + + this.hFamily := Gdip_FontFamilyCreate(this.fontName) + this.hFont := Gdip_FontCreate(this.hFamily, this.fontSize * this.dpiSacle, regular := 0) + this.hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip + Gdip_SetStringFormatAlign(this.hFormat, left := 0) ; left:0, center:1, right:2 + + local hDC := GetDC(this.gui.Hwnd) + local pGraphics := Gdip_GraphicsFromHDC(hDC) + + CreateRectF(&RC, 0, 0, 0, 0) + ; Measure preedit texts + this.prdSelSize := this.MeasureString(pGraphics, this.prdSelTxt, this.hFont, this.hFormat, &RC) + this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.hFont, this.hFormat, &RC) + this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.hFont, this.hFormat, &RC) + + ; Measure candidate texts + this.candRowSizes := [] + maxRowWidth := this.prdSelSize.w + this.padding + this.prdHlSelSize.w + this.prdHlUnselSize.w + totalHeight := this.prdHlSelSize.h + this.lineSpacing + + has_label := !!context.select_labels[0] + select_keys := menu.select_keys + num_select_keys := StrLen(select_keys) + + Loop this.num_candidates { + labelText := String(A_Index) + if A_Index <= menu.page_size && has_label + labelText := context.select_labels[A_Index] || labelText + else if A_Index <= num_select_keys + labelText := SubStr(select_keys, A_Index, 1) + labelText := Format(UIStyle.label_format, labelText) + labelInfo := this.MeasureString(pGraphics, labelText, this.hFont, this.hFormat, &RC) + labelInfo.text := labelText + this.labelsInfoArray.Push(labelInfo) + + candText := cands[A_Index].text + candInfo := this.MeasureString(pGraphics, candText, this.hFont, this.hFormat, &RC) + candInfo.text := candText + this.candsInfoArray.Push(candInfo) + + commentText := cands[A_Index].comment + commentInfo := this.MeasureString(pGraphics, commentText, this.hFont, this.hFormat, &RC) + commentInfo.text := commentText + this.commentsInfoArray.Push(commentInfo) + + rowSize := { + w: labelInfo.w + candInfo.w + (commentText ? this.padding * 2 + commentInfo.w : 0), + h: candInfo.h + } + this.candRowSizes.Push(rowSize) + if (rowSize.w > maxRowWidth) { + maxRowWidth := rowSize.w + } + totalHeight += candInfo.h + this.lineSpacing + } + + ; get better spacing to align comments + Loop this.num_candidates { + this.commentsInfoArray[A_Index].spacing := maxRowWidth - this.labelsInfoArray[A_Index].w - this.candsInfoArray[A_Index].w - this.commentsInfoArray[A_Index].w + } + + Gdip_DeleteGraphics(pGraphics) + ReleaseDC(hDC, this.gui.Hwnd) + + this.boxWidth := Max((Ceil(maxRowWidth) + this.padding * 2 + this.borderWidth * 2), UIStyle.min_width) + this.boxHeight := Ceil(totalHeight) + this.padding * 2 + this.borderWidth * 2 - Round(this.lineSpacing / 2) + calcW := this.boxWidth + calcH := this.boxHeight + } + + Show(x, y) { + if (this.gui && CandidateBox.isHidden) { + this.gui.Show("NA") + CandidateBox.isHidden := 0 + } + + this.hDC := CreateCompatibleDC() + this.hBitmap := CreateDIBSection(this.boxWidth, this.boxHeight) + this.oBitmap := SelectObject(this.hDC, this.hBitmap) + this.pGraphics := Gdip_GraphicsFromHDC(this.hDC) + Gdip_SetTextRenderingHint(this.pGraphics, AntiAliasGridFit := 3) + Gdip_SetSmoothingMode(this.pGraphics, AntiAlias := 4) + + ; Draw border + if (this.borderWidth > 0) { + pBrushBorder := Gdip_BrushCreateSolid(this.borderColor) + this.FillRoundedRect(this.pGraphics, pBrushBorder, 0, 0, this.boxWidth, this.boxHeight, this.boxCornerR) + Gdip_DeleteBrush(pBrushBorder) + } + + ; Draw background + pBrushBg := Gdip_BrushCreateSolid(this.backgroundColor) + bgX := this.borderWidth, bgY := this.borderWidth + bgW := this.boxWidth - this.borderWidth * 2 + bgH := this.boxHeight - this.borderWidth * 2 + bgCornerRadius := this.boxCornerR > this.borderWidth ? this.boxCornerR - this.borderWidth : 0 + this.FillRoundedRect(this.pGraphics, pBrushBg, bgX, bgY, bgW, bgH, bgCornerRadius) + Gdip_DeleteBrush(pBrushBg) + + ; Draw preedit + rectShrink := 2 + currentY := this.padding + this.borderWidth + prdSelTxtRc := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } + prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } + prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } + this.DrawText(this.pGraphics, this.prdSelTxt, prdSelTxtRc, this.textColor) + pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) + Gdip_DeleteBrush(pBrsh_hlSelBg) + this.DrawText(this.pGraphics, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) + this.DrawText(this.pGraphics, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) + currentY += this.prdSelSize.h + this.lineSpacing + + ; Draw candidates + Loop this.num_candidates { + rowSize := this.candRowSizes[A_Index] + labelFg := this.labelColor + candFg := this.candTxtColor + commentFg := this.commentTxtColor + if (A_Index == this.hilited_index) { ; Draw highlight if selected + labelFg := this.hlLabelColor + candFg := this.hlCandTxtColor + commentFg := this.hlCommentTxtColor + pBrsh_hlCandBg := Gdip_BrushCreateSolid(this.hlCandBgColor) + highlightX := this.borderWidth + this.padding / 2 + highlightY := currentY - this.lineSpacing / 2 + highlightW := this.boxWidth - this.borderWidth * 2 - this.padding + highlightH := rowSize.h + this.lineSpacing - rectShrink + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, highlightX, highlightY, highlightW, highlightH, this.hlCornerR) + Gdip_DeleteBrush(pBrsh_hlCandBg) + } + + labelRect := { x: this.padding + this.borderWidth, y: currentY, w: this.labelsInfoArray[A_Index].w, h: rowSize.h } + candRect := { x: labelRect.x + labelRect.w, y: currentY, w: this.candsInfoArray[A_Index].w, h: rowSize.h } + this.DrawText(this.pGraphics, this.labelsInfoArray[A_Index].text, labelRect, labelFg) + this.DrawText(this.pGraphics, this.candsInfoArray[A_Index].text, candRect, candFg) + + commentW := this.commentsInfoArray[A_Index].w + if commentW > 0 { + commentRect := { x: candRect.x + candRect.w + this.commentsInfoArray[A_Index].spacing, y: currentY, w: commentW, h: rowSize.h } + this.DrawText(this.pGraphics, this.commentsInfoArray[A_Index].text, commentRect, commentFg) + } + + currentY += rowSize.h + this.lineSpacing + } + + UpdateLayeredWindow(this.gui.Hwnd, this.hDC, x, y, this.boxWidth, this.boxHeight) + + this.ReleaseDrawingSurface() + } + + Hide() { + if (this.gui && !CandidateBox.isHidden) { + this.gui.Show("Hide") + CandidateBox.isHidden := 1 + } + } + + ReleaseFont() { + if (this.hFont) + Gdip_DeleteFont(this.hFont) + if (this.hFamily) + Gdip_DeleteFontFamily(this.hFamily) + if (this.hFormat) + Gdip_DeleteStringFormat(this.hFormat) + } + + ReleaseDrawingSurface() { + if (this.pGraphics) { + Gdip_DeleteGraphics(this.pGraphics) + this.pGraphics := 0 + } + if (this.hBitmap) { + DeleteObject(this.hBitmap) + this.hBitmap := 0 + } + if (this.hDC) { + SelectObject(this.hDC, this.oBitmap) + DeleteDC(this.hDC) + this.hDC := 0 + } + } + + ReleaseAll() { + this.ReleaseFont() + this.ReleaseDrawingSurface() + + if (this.pToken) { + Gdip_Shutdown(this.pToken) + this.pToken := 0 + } + if (this.gui) { + this.gui.Destroy() + } + } + + MeasureString(pGraphics, text, hFont, hFormat, &RectF) { + if !text + return { w: 0, h: 32 } + + rc := Buffer(16) + ; !Notice, this way gets incorrect dim in test + ; dim := Gdip_MeasureString(pGraphics, text, hFont, hFormat, &rc) + ; rect := StrSplit(dim, "|") + ; return { w: Round(rect[3]), h: Round(rect[4]) } + + DllCall("gdiplus\GdipMeasureString", + "Ptr", pGraphics, + "WStr", text, + "Int", -1, + "Ptr", hFont, + "Ptr", RectF.Ptr, + "Ptr", hFormat, + "Ptr", rc.Ptr, + "UInt*", 0, + "UInt*", 0, + "Int") + + return { w: NumGet(rc.Ptr, 8, "Float"), h: NumGet(rc.Ptr, 12, "Float") } + } + + DrawText(pGraphics, text, textRect, color) { + this.pBrush := Gdip_BrushCreateSolid(color) + CreateRectF(&RC, textRect.x, textRect.y, textRect.w, textRect.h) + Gdip_DrawString(pGraphics, text, this.hFont, this.hFormat, this.pBrush, &RC) + Gdip_DeleteBrush(this.pBrush) + } + + FillRoundedRect(pGraphics, pBrush, x, y, w, h, r) { + if (r <= 0) { + Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) + } else { + Gdip_FillRoundedRectangle(pGraphics, pBrush, x, y, w, h, r) + } + } +} + +/* class CandidateBox { static dbg := false static gui := 0 @@ -323,8 +638,9 @@ class CandidateBox { } } } +*/ -GetCompositionText(&composition, &pre_selected, &selected, &post_selected) { +GetCompositionText(composition, &pre_selected, &selected, &post_selected) { pre_selected := "" selected := "" post_selected := "" @@ -376,4 +692,4 @@ GetCompositionText(&composition, &pre_selected, &selected, &post_selected) { pre_selected := StrGet(preedit_buffer, "UTF-8") return false } -} +} \ No newline at end of file diff --git a/Lib/RabbitConfig.ahk b/Lib/RabbitConfig.ahk index a73dadf..056d96a 100644 --- a/Lib/RabbitConfig.ahk +++ b/Lib/RabbitConfig.ahk @@ -59,10 +59,10 @@ class RabbitConfig { if rime.config_test_get_bool(config, "fix_candidate_box", &result) RabbitConfig.fix_candidate_box := !!result - UIStyle.Update(&config, true) + UIStyle.Update(config, true) if IS_DARK_MODE := RabbitIsUserDarkMode() { if color_name := rime.config_get_string(config, "style/color_scheme_dark") - UIStyle.use_dark := UIStyle.UpdateColor(&config, color_name) + UIStyle.use_dark := UIStyle.UpdateColor(config, color_name) DarkMode.set(IS_DARK_MODE) } diff --git a/Lib/RabbitThemesUI.ahk b/Lib/RabbitThemesUI.ahk index 9b796b3..3ecf5c4 100644 --- a/Lib/RabbitThemesUI.ahk +++ b/Lib/RabbitThemesUI.ahk @@ -17,14 +17,9 @@ */ #Include -#Include +#Include class CandidatePreview { - borderWidth := 2 - cornerRadius := 4 - lineSpacing := 6 - padding := 8 - pToken := 0 pBitmap := 0 hBitmap := 0 @@ -42,6 +37,13 @@ class CandidatePreview { } this.imgCtrl := ctrl this.dpiSacle := GUIUtilities.GetMonitorDpiScale() + + this.borderWidth := UIStyle.border_width + this.borderColor := UIStyle.border_color + this.cornerRadius := UIStyle.corner_radius + this.lineSpacing := UIStyle.margin_y + this.padding := UIStyle.margin_x + ; only use one font to preview this.fontName := theme.HasOwnProp("font_face") ? theme.font_face : UIStyle.font_face this.fontSize := theme.HasOwnProp("font_point") ? theme.font_point : UIStyle.font_point @@ -94,6 +96,7 @@ class CandidatePreview { this.pBitmap := Gdip_CreateBitmap(this.previewWidth, this.previewHeight) this.pGraphics := Gdip_GraphicsFromImage(this.pBitmap) Gdip_SetSmoothingMode(this.pGraphics, AntiAlias := 4) + Gdip_SetTextRenderingHint(this.pGraphics, AntiAlias := 4) ; Draw border if (this.borderWidth > 0) { @@ -211,7 +214,6 @@ class CandidatePreview { this.pBrush := Gdip_BrushCreateSolid(color) CreateRectF(&RC, textRect.x, textRect.y, textRect.w, textRect.h) Gdip_DrawString(pGraphics, text, this.hFont, this.hFormat, this.pBrush, &RC) - Gdip_SetTextRenderingHint(this.pGraphics, AntiAlias := 4) Gdip_DeleteBrush(this.pBrush) } diff --git a/Lib/RabbitUIStyle.ahk b/Lib/RabbitUIStyle.ahk index f47121c..f0b05f4 100644 --- a/Lib/RabbitUIStyle.ahk +++ b/Lib/RabbitUIStyle.ahk @@ -28,10 +28,14 @@ class UIStyle { static comment_font_point := 14 static label_format := "{}. " - static margin_x := 5 - static margin_y := 5 + static border_width := 2 + static corner_radius := 6 + static round_corner := 4 + static margin_x := 6 + static margin_y := 6 static min_width := 160 + static border_color := 0xffe0e0e0 static text_color := 0xff000000 static back_color := 0xffeeeeec static candidate_text_color := 0xff000000 @@ -45,7 +49,7 @@ class UIStyle { static hilited_label_color := 0xffffffff static hilited_comment_text_color := 0xff000000 - static Update(&config, initialize) { + static Update(config, initialize) { global rime if !rime || !config return @@ -70,6 +74,10 @@ class UIStyle { UIStyle.comment_font_point := 14 if rime.config_test_get_string(config, "style/label_format", &fmt) && fmt UIStyle.label_format := fmt + if rime.config_test_get_int(config, "style/layout/corner_radius", &cr) && cr >= 0 + UIStyle.corner_radius := cr + if rime.config_test_get_int(config, "style/layout/round_corner", &r) && r >= 0 + UIStyle.round_corner := r if rime.config_test_get_int(config, "style/layout/margin_x", &mx) && mx >= 0 UIStyle.margin_x := mx if rime.config_test_get_int(config, "style/layout/margin_y", &my) && my >= 0 @@ -77,10 +85,10 @@ class UIStyle { if rime.config_test_get_int(config, "style/layout/min_width", &w) && w >= 0 UIStyle.min_width := w if initialize and color := rime.config_get_string(config, "style/color_scheme") - UIStyle.UpdateColor(&config, color) + UIStyle.UpdateColor(config, color) } - static UpdateColor(&config, color) { + static UpdateColor(config, color) { global rime if color or (buffer := rime.config_get_string(config, "style/color_scheme")) { local prefix := "preset_color_schemes/" . (color ? color : buffer) @@ -90,18 +98,19 @@ class UIStyle { fmt := cfmt } - UIStyle.text_color := UIStyle.GetColor(&config, prefix . "/text_color", fmt, 0xff000000) - UIStyle.back_color := UIStyle.GetColor(&config, prefix . "/back_color", fmt, 0xffeceeee) - UIStyle.candidate_text_color := UIStyle.GetColor(&config, prefix . "/candidate_text_color", fmt, UIStyle.text_color) - UIStyle.candidate_back_color := UIStyle.GetColor(&config, prefix . "/candidate_back_color", fmt, UIStyle.back_color) - UIStyle.label_color := UIStyle.GetColor(&config, prefix . "/label_color", fmt, UIStyle.BlendColors(UIStyle.candidate_text_color, UIStyle.candidate_back_color)) - UIStyle.comment_text_color := UIStyle.GetColor(&config, prefix . "/comment_text_color", fmt, UIStyle.label_color) - UIStyle.hilited_text_color := UIStyle.GetColor(&config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) - UIStyle.hilited_back_color := UIStyle.GetColor(&config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) - UIStyle.hilited_candidate_text_color := UIStyle.GetColor(&config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) - UIStyle.hilited_candidate_back_color := UIStyle.GetColor(&config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) - UIStyle.hilited_label_color := UIStyle.GetColor(&config, prefix . "/hilited_label_color", fmt, UIStyle.BlendColors(UIStyle.hilited_candidate_text_color, UIStyle.hilited_candidate_back_color)) - UIStyle.hilited_comment_text_color := UIStyle.GetColor(&config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_label_color) + UIStyle.border_color := UIStyle.GetColor(config, prefix . "/border_color", fmt, 0xffe0e0e0) + UIStyle.text_color := UIStyle.GetColor(config, prefix . "/text_color", fmt, 0xff000000) + UIStyle.back_color := UIStyle.GetColor(config, prefix . "/back_color", fmt, 0xffeceeee) + UIStyle.candidate_text_color := UIStyle.GetColor(config, prefix . "/candidate_text_color", fmt, UIStyle.text_color) + UIStyle.candidate_back_color := UIStyle.GetColor(config, prefix . "/candidate_back_color", fmt, UIStyle.back_color) + UIStyle.label_color := UIStyle.GetColor(config, prefix . "/label_color", fmt, UIStyle.BlendColors(UIStyle.candidate_text_color, UIStyle.candidate_back_color)) + UIStyle.comment_text_color := UIStyle.GetColor(config, prefix . "/comment_text_color", fmt, UIStyle.label_color) + UIStyle.hilited_text_color := UIStyle.GetColor(config, prefix . "/hilited_text_color", fmt, UIStyle.text_color) + UIStyle.hilited_back_color := UIStyle.GetColor(config, prefix . "/hilited_back_color", fmt, UIStyle.back_color) + UIStyle.hilited_candidate_text_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_text_color", fmt, UIStyle.hilited_text_color) + UIStyle.hilited_candidate_back_color := UIStyle.GetColor(config, prefix . "/hilited_candidate_back_color", fmt, UIStyle.hilited_back_color) + UIStyle.hilited_label_color := UIStyle.GetColor(config, prefix . "/hilited_label_color", fmt, UIStyle.BlendColors(UIStyle.hilited_candidate_text_color, UIStyle.hilited_candidate_back_color)) + UIStyle.hilited_comment_text_color := UIStyle.GetColor(config, prefix . "/hilited_comment_text_color", fmt, UIStyle.hilited_label_color) return true } @@ -132,7 +141,7 @@ class UIStyle { return (Integer(retAlpha) * 255 << 24) | (retR << 16) | (retG << 8) | retB } - static GetColor(&config, key, fmt, fallback) { + static GetColor(config, key, fmt, fallback) { global rime if not rime.config_test_get_string(config, key, &color) return fallback @@ -208,10 +217,10 @@ OnColorChange(wParam, lParam, msg, hWnd) { IS_DARK_MODE := RabbitIsUserDarkMode() if old_dark != IS_DARK_MODE { if config := rime.config_open("rabbit") { - UIStyle.Update(&config, true) + UIStyle.Update(config, true) if IS_DARK_MODE { if color_name := rime.config_get_string(config, "style/color_scheme_dark") - UIStyle.use_dark := UIStyle.UpdateColor(&config, color_name) + UIStyle.use_dark := UIStyle.UpdateColor(config, color_name) } rime.config_close(config) diff --git a/Rabbit.ahk b/Rabbit.ahk index 9efd95f..2e0c55e 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -385,12 +385,12 @@ ProcessKey(key, mask, this_hotkey) { info := MonitorManage.GetMonitorInfo(hMon) show_at_left_top := !!info if show_at_left_top && !last_is_hide { - box.Build(&context, &box_width, &box_height) + box.Build(context, &box_width, &box_height) box.Show(info.work.left + 4, info.work.top + 4) } } if !show_at_left_top && GetCaretPos(&caret_x, &caret_y, &caret_w, &caret_h) { - box.Build(&context, &box_width, &box_height) + box.Build(context, &box_width, &box_height) if RabbitConfig.fix_candidate_box && prev_show { new_x := prev_x new_y := prev_y @@ -424,7 +424,7 @@ ProcessKey(key, mask, this_hotkey) { CoordMode("Mouse", "Screen") MouseGetPos(&mouse_x, &mouse_y) CoordMode("Mouse", backup_mouse_ref) - box.Build(&context, &box_width, &box_height) + box.Build(context, &box_width, &box_height) box.Show(mouse_x, mouse_y) } prev_show := true From 4fa32125631e2fbe5fd3362140438d64bd10a239 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Fri, 29 Aug 2025 14:58:41 +0800 Subject: [PATCH 18/38] chore: use submodule for Gdip --- .github/workflows/ci.yaml | 2 + .gitmodules | 3 + Lib/Gdip | 1 + Lib/Gdip/Gdip_All.ahk | 3106 ------------------------------------ Lib/RabbitCandidateBox.ahk | 1 + README.md | 1 + 6 files changed, 8 insertions(+), 3106 deletions(-) create mode 160000 Lib/Gdip delete mode 100644 Lib/Gdip/Gdip_All.ahk diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dc687ae..b923154 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -155,6 +155,7 @@ jobs: with: name: Rabbit-${{ matrix.target }} path: | + Lib/Gdip/Gdip_All.ahk Lib/librime-ahk/*.ahk Lib/librime-ahk/rime.dll Lib/librime-ahk/utils @@ -175,6 +176,7 @@ jobs: name: Rabbit-Full-${{ matrix.target }} path: | Data + Lib/Gdip/Gdip_All.ahk Lib/librime-ahk/*.ahk Lib/librime-ahk/rime.dll Lib/librime-ahk/utils diff --git a/.gitmodules b/.gitmodules index 37cdce0..48fc516 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "plum"] path = plum url = https://github.com/rime/plum +[submodule "Lib/Gdip"] + path = Lib/Gdip + url = https://github.com/buliasz/AHKv2-Gdip diff --git a/Lib/Gdip b/Lib/Gdip new file mode 160000 index 0000000..9fa1817 --- /dev/null +++ b/Lib/Gdip @@ -0,0 +1 @@ +Subproject commit 9fa18174be46326bc640dbfb542ac2c5d9399f44 diff --git a/Lib/Gdip/Gdip_All.ahk b/Lib/Gdip/Gdip_All.ahk deleted file mode 100644 index 584d593..0000000 --- a/Lib/Gdip/Gdip_All.ahk +++ /dev/null @@ -1,3106 +0,0 @@ -; [AHKv2-Gdip](https://github.com/buliasz/AHKv2-Gdip) - -; @tic Created the original Gdip.ahk library. -; @Rseding91 Updated it to make it compatible with unicode and x64 AHK versions and renamed the file Gdip_All.ahk. -; @mmikeww Repository updates @Rseding91's Gdip_All.ahk to fix bugs and make it compatible with AHK v2. -; @buliasz Fork of mmikeww repository: updates for the current version of AHK v2 (dropping AHK v1 backward compatibility). - -; v1.62 -; -;##################################################################################### -;##################################################################################### -; STATUS ENUMERATION -; Return values for functions specified to have status enumerated return type -;##################################################################################### -; -; Ok = 0 -; GenericError = 1 -; InvalidParameter = 2 -; OutOfMemory = 3 -; ObjectBusy = 4 -; InsufficientBuffer = 5 -; NotImplemented = 6 -; Win32Error = 7 -; WrongState = 8 -; Aborted = 9 -; FileNotFound = 10 -; ValueOverflow = 11 -; AccessDenied = 12 -; UnknownImageFormat = 13 -; FontFamilyNotFound = 14 -; FontStyleNotFound = 15 -; NotTrueTypeFont = 16 -; UnsupportedGdiplusVersion = 17 -; GdiplusNotInitialized = 18 -; PropertyNotFound = 19 -; PropertyNotSupported = 20 -; ProfileNotFound = 21 -; -;##################################################################################### -;##################################################################################### -; FUNCTIONS -;##################################################################################### -; -; UpdateLayeredWindow(hwnd, hdc, x:="", y:="", w:="", h:="", Alpha:=255) -; BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster:="") -; StretchBlt(dDC, dx, dy, dw, dh, sDC, sx, sy, sw, sh, Raster:="") -; SetImage(hwnd, hBitmap) -; Gdip_BitmapFromScreen(Screen:=0, Raster:="") -; CreateRectF(&RectF, x, y, w, h) -; CreateSizeF(&SizeF, w, h) -; CreateDIBSection -; -;##################################################################################### - -UpdateLayeredWindow(hwnd, hdc, x:="", y:="", w:="", h:="", Alpha:=255) -{ - if ((x != "") && (y != "")) { - pt := Buffer(8) - NumPut("UInt", x, "UInt", y, pt) - } - - if (w = "") || (h = "") { - WinGetRect(hwnd,,, &w, &h) - } - - return DllCall("UpdateLayeredWindow" - , "UPtr", hwnd - , "UPtr", 0 - , "UPtr", ((x = "") && (y = "")) ? 0 : pt.Ptr - , "Int64*", w|h<<32 - , "UPtr", hdc - , "Int64*", 0 - , "UInt", 0 - , "UInt*", Alpha<<16|1<<24 - , "UInt", 2) -} - -;##################################################################################### - -; Function BitBlt -; Description The BitBlt function performs a bit-block transfer of the color data corresponding to a rectangle -; of pixels from the specified source device context into a destination device context. -; -; dDC handle to destination DC -; dx x-coord of destination upper-left corner -; dy y-coord of destination upper-left corner -; dw width of the area to copy -; dh height of the area to copy -; sDC handle to source DC -; sx x-coordinate of source upper-left corner -; sy y-coordinate of source upper-left corner -; Raster raster operation code -; -; return if the function succeeds, the return value is nonzero -; -; notes if no raster operation is specified, then SRCCOPY is used, which copies the source directly to the destination rectangle -; -; BLACKNESS = 0x00000042 -; NOTSRCERASE = 0x001100A6 -; NOTSRCCOPY = 0x00330008 -; SRCERASE = 0x00440328 -; DSTINVERT = 0x00550009 -; PATINVERT = 0x005A0049 -; SRCINVERT = 0x00660046 -; SRCAND = 0x008800C6 -; MERGEPAINT = 0x00BB0226 -; MERGECOPY = 0x00C000CA -; SRCCOPY = 0x00CC0020 -; SRCPAINT = 0x00EE0086 -; PATCOPY = 0x00F00021 -; PATPAINT = 0x00FB0A09 -; WHITENESS = 0x00FF0062 -; CAPTUREBLT = 0x40000000 -; NOMIRRORBITMAP = 0x80000000 - -BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster:="") -{ - return DllCall("gdi32\BitBlt" - , "UPtr", dDC - , "Int", dx - , "Int", dy - , "Int", dw - , "Int", dh - , "UPtr", sDC - , "Int", sx - , "Int", sy - , "UInt", Raster ? Raster : 0x00CC0020) -} - -;##################################################################################### - -; Function StretchBlt -; Description The StretchBlt function copies a bitmap from a source rectangle into a destination rectangle, -; stretching or compressing the bitmap to fit the dimensions of the destination rectangle, if necessary. -; The system stretches or compresses the bitmap according to the stretching mode currently set in the destination device context. -; -; ddc handle to destination DC -; dx x-coord of destination upper-left corner -; dy y-coord of destination upper-left corner -; dw width of destination rectangle -; dh height of destination rectangle -; sdc handle to source DC -; sx x-coordinate of source upper-left corner -; sy y-coordinate of source upper-left corner -; sw width of source rectangle -; sh height of source rectangle -; Raster raster operation code -; -; return if the function succeeds, the return value is nonzero -; -; notes if no raster operation is specified, then SRCCOPY is used. It uses the same raster operations as BitBlt - -StretchBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, sw, sh, Raster:="") -{ - return DllCall("gdi32\StretchBlt" - , "UPtr", ddc - , "Int", dx - , "Int", dy - , "Int", dw - , "Int", dh - , "UPtr", sdc - , "Int", sx - , "Int", sy - , "Int", sw - , "Int", sh - , "UInt", Raster ? Raster : 0x00CC0020) -} - -;##################################################################################### - -; Function SetStretchBltMode -; Description The SetStretchBltMode function sets the bitmap stretching mode in the specified device context -; -; hdc handle to the DC -; iStretchMode The stretching mode, describing how the target will be stretched -; -; return if the function succeeds, the return value is the previous stretching mode. If it fails it will return 0 -; -; STRETCH_ANDSCANS = 0x01 -; STRETCH_ORSCANS = 0x02 -; STRETCH_DELETESCANS = 0x03 -; STRETCH_HALFTONE = 0x04 - -SetStretchBltMode(hdc, iStretchMode:=4) -{ - return DllCall("gdi32\SetStretchBltMode" - , "UPtr", hdc - , "Int", iStretchMode) -} - -;##################################################################################### - -; Function SetImage -; Description Associates a new image with a static control -; -; hwnd handle of the control to update -; hBitmap a gdi bitmap to associate the static control with -; -; return if the function succeeds, the return value is nonzero - -SetImage(hwnd, hBitmap) -{ - _E := DllCall( "SendMessage", "UPtr", hwnd, "UInt", 0x172, "UInt", 0x0, "UPtr", hBitmap ) - DeleteObject(_E) - return _E -} - -;##################################################################################### - -; Function SetSysColorToControl -; Description Sets a solid colour to a control -; -; hwnd handle of the control to update -; SysColor A system colour to set to the control -; -; return if the function succeeds, the return value is zero -; -; notes A control must have the 0xE style set to it so it is recognised as a bitmap -; By default SysColor=15 is used which is COLOR_3DFACE. This is the standard background for a control -; -; COLOR_3DDKSHADOW = 21 -; COLOR_3DFACE = 15 -; COLOR_3DHIGHLIGHT = 20 -; COLOR_3DHILIGHT = 20 -; COLOR_3DLIGHT = 22 -; COLOR_3DSHADOW = 16 -; COLOR_ACTIVEBORDER = 10 -; COLOR_ACTIVECAPTION = 2 -; COLOR_APPWORKSPACE = 12 -; COLOR_BACKGROUND = 1 -; COLOR_BTNFACE = 15 -; COLOR_BTNHIGHLIGHT = 20 -; COLOR_BTNHILIGHT = 20 -; COLOR_BTNSHADOW = 16 -; COLOR_BTNTEXT = 18 -; COLOR_CAPTIONTEXT = 9 -; COLOR_DESKTOP = 1 -; COLOR_GRADIENTACTIVECAPTION = 27 -; COLOR_GRADIENTINACTIVECAPTION = 28 -; COLOR_GRAYTEXT = 17 -; COLOR_HIGHLIGHT = 13 -; COLOR_HIGHLIGHTTEXT = 14 -; COLOR_HOTLIGHT = 26 -; COLOR_INACTIVEBORDER = 11 -; COLOR_INACTIVECAPTION = 3 -; COLOR_INACTIVECAPTIONTEXT = 19 -; COLOR_INFOBK = 24 -; COLOR_INFOTEXT = 23 -; COLOR_MENU = 4 -; COLOR_MENUHILIGHT = 29 -; COLOR_MENUBAR = 30 -; COLOR_MENUTEXT = 7 -; COLOR_SCROLLBAR = 0 -; COLOR_WINDOW = 5 -; COLOR_WINDOWFRAME = 6 -; COLOR_WINDOWTEXT = 8 - -SetSysColorToControl(hwnd, SysColor:=15) -{ - WinGetRect(hwnd,,, &w, &h) - bc := DllCall("GetSysColor", "Int", SysColor, "UInt") - pBrushClear := Gdip_BrushCreateSolid(0xff000000 | (bc >> 16 | bc & 0xff00 | (bc & 0xff) << 16)) - pBitmap := Gdip_CreateBitmap(w, h), G := Gdip_GraphicsFromImage(pBitmap) - Gdip_FillRectangle(G, pBrushClear, 0, 0, w, h) - hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap) - SetImage(hwnd, hBitmap) - Gdip_DeleteBrush(pBrushClear) - Gdip_DeleteGraphics(G), Gdip_DisposeImage(pBitmap), DeleteObject(hBitmap) - return 0 -} - -;##################################################################################### - -; Function Gdip_BitmapFromScreen -; Description Gets a gdi+ bitmap from the screen -; -; Screen 0 = All screens -; Any numerical value = Just that screen -; x|y|w|h = Take specific coordinates with a width and height -; Raster raster operation code -; -; return if the function succeeds, the return value is a pointer to a gdi+ bitmap -; -1: one or more of x,y,w,h not passed properly -; -; notes if no raster operation is specified, then SRCCOPY is used to the returned bitmap - -Gdip_BitmapFromScreen(Screen:=0, Raster:="") -{ - hhdc := 0 - if (Screen = 0) { - _x := DllCall( "GetSystemMetrics", "Int", 76 ) - _y := DllCall( "GetSystemMetrics", "Int", 77 ) - _w := DllCall( "GetSystemMetrics", "Int", 78 ) - _h := DllCall( "GetSystemMetrics", "Int", 79 ) - } - else if (SubStr(Screen, 1, 5) = "hwnd:") { - Screen := SubStr(Screen, 6) - if !WinExist("ahk_id " Screen) { - return -2 - } - WinGetRect(Screen,,, &_w, &_h) - _x := _y := 0 - hhdc := GetDCEx(Screen, 3) - } - else if IsInteger(Screen) { - M := GetMonitorInfo(Screen) - _x := M.Left, _y := M.Top, _w := M.Right-M.Left, _h := M.Bottom-M.Top - } - else { - S := StrSplit(Screen, "|") - _x := S[1], _y := S[2], _w := S[3], _h := S[4] - } - - if (_x = "") || (_y = "") || (_w = "") || (_h = "") { - return -1 - } - - chdc := CreateCompatibleDC() - hbm := CreateDIBSection(_w, _h, chdc) - obm := SelectObject(chdc, hbm) - hhdc := hhdc ? hhdc : GetDC() - BitBlt(chdc, 0, 0, _w, _h, hhdc, _x, _y, Raster) - ReleaseDC(hhdc) - - pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm) - - SelectObject(chdc, obm) - DeleteObject(hbm) - DeleteDC(hhdc) - DeleteDC(chdc) - return pBitmap -} - -;##################################################################################### - -; Function Gdip_BitmapFromHWND -; Description Uses PrintWindow to get a handle to the specified window and return a bitmap from it -; -; hwnd handle to the window to get a bitmap from -; -; return if the function succeeds, the return value is a pointer to a gdi+ bitmap -; -; notes Window must not be not minimised in order to get a handle to it's client area - -Gdip_BitmapFromHWND(hwnd) -{ - WinGetRect(hwnd,,, &Width, &Height) - hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm) - PrintWindow(hwnd, hdc) - pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm) - SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc) - return pBitmap -} - -;##################################################################################### - -; Function CreateRectF -; Description Creates a RectF object, containing a the coordinates and dimensions of a rectangle -; -; RectF Name to call the RectF object -; x x-coordinate of the upper left corner of the rectangle -; y y-coordinate of the upper left corner of the rectangle -; w Width of the rectangle -; h Height of the rectangle -; -; return No return value - -CreateRectF(&RectF, x, y, w, h) -{ - RectF := Buffer(16) - NumPut( - "Float", x, - "Float", y, - "Float", w, - "Float", h, - RectF) -} - -;##################################################################################### - -; Function CreateRect -; Description Creates a Rect object, containing a the coordinates and dimensions of a rectangle -; -; RectF Name to call the RectF object -; x x-coordinate of the upper left corner of the rectangle -; y y-coordinate of the upper left corner of the rectangle -; w Width of the rectangle -; h Height of the rectangle -; -; return No return value -CreateRect(&Rect, x, y, w, h) -{ - Rect := Buffer(16) - NumPut("UInt", x, "UInt", y, "UInt", w, "UInt", h, Rect) -} -;##################################################################################### - -; Function CreateSizeF -; Description Creates a SizeF object, containing an 2 values -; -; SizeF Name to call the SizeF object -; w w-value for the SizeF object -; h h-value for the SizeF object -; -; return No Return value - -CreateSizeF(&SizeF, w, h) -{ - SizeF := Buffer(8) - NumPut("Float", w, "Float", h, SizeF) -} -;##################################################################################### - -; Function CreatePointF -; Description Creates a SizeF object, containing an 2 values -; -; SizeF Name to call the SizeF object -; w w-value for the SizeF object -; h h-value for the SizeF object -; -; return No Return value - -CreatePointF(&PointF, x, y) -{ - PointF := Buffer(8) - NumPut("Float", x, "Float", y, PointF) -} -;##################################################################################### - -; Function CreateDIBSection -; Description The CreateDIBSection function creates a DIB (Device Independent Bitmap) that applications can write to directly -; -; w width of the bitmap to create -; h height of the bitmap to create -; hdc a handle to the device context to use the palette from -; bpp bits per pixel (32 = ARGB) -; ppvBits A pointer to a variable that receives a pointer to the location of the DIB bit values -; -; return returns a DIB. A gdi bitmap -; -; notes ppvBits will receive the location of the pixels in the DIB - -CreateDIBSection(w, h, hdc:="", bpp:=32, &ppvBits:=0) -{ - hdc2 := hdc ? hdc : GetDC() - bi := Buffer(40, 0) - - NumPut("UInt", 40, "UInt", w, "UInt", h, "ushort", 1, "ushort", bpp, "UInt", 0, bi) - - hbm := DllCall("CreateDIBSection" - , "UPtr", hdc2 - , "UPtr", bi.Ptr - , "UInt", 0 - , "UPtr*", &ppvBits - , "UPtr", 0 - , "UInt", 0, "UPtr") - - if (!hdc) { - ReleaseDC(hdc2) - } - return hbm -} - -;##################################################################################### - -; Function PrintWindow -; Description The PrintWindow function copies a visual window into the specified device context (DC), typically a printer DC -; -; hwnd A handle to the window that will be copied -; hdc A handle to the device context -; Flags Drawing options -; -; return if the function succeeds, it returns a nonzero value -; -; PW_CLIENTONLY = 1 - -PrintWindow(hwnd, hdc, Flags:=0) -{ - return DllCall("PrintWindow", "UPtr", hwnd, "UPtr", hdc, "UInt", Flags) -} - -;##################################################################################### - -; Function DestroyIcon -; Description Destroys an icon and frees any memory the icon occupied -; -; hIcon Handle to the icon to be destroyed. The icon must not be in use -; -; return if the function succeeds, the return value is nonzero - -DestroyIcon(hIcon) -{ - return DllCall("DestroyIcon", "UPtr", hIcon) -} - -;##################################################################################### - -; Function: GetIconDimensions -; Description: Retrieves a given icon/cursor's width and height -; -; hIcon Pointer to an icon or cursor -; Width ByRef variable. This variable is set to the icon's width -; Height ByRef variable. This variable is set to the icon's height -; -; return if the function succeeds, the return value is zero, otherwise: -; -1 = Could not retrieve the icon's info. Check A_LastError for extended information -; -2 = Could not delete the icon's bitmask bitmap -; -3 = Could not delete the icon's color bitmap - -GetIconDimensions(hIcon, &Width:=0, &Height:=0) { - ICONINFO := Buffer(size := 16 + 2 * A_PtrSize, 0) - - if !DllCall("user32\GetIconInfo", "UPtr", hIcon, "UPtr", ICONINFO.Ptr) { - return -1 - } - - hbmMask := NumGet(ICONINFO.Ptr, 16, "UPtr") - hbmColor := NumGet(ICONINFO.Ptr, 16 + A_PtrSize, "UPtr") - BITMAP := Buffer(size, 0) - - if DllCall("gdi32\GetObject", "UPtr", hbmColor, "Int", size, "UPtr", BITMAP.Ptr) { - Width := NumGet(BITMAP.Ptr, 4, "Int") - Height := NumGet(BITMAP.Ptr, 8, "Int") - } - - if !DllCall("gdi32\DeleteObject", "UPtr", hbmMask) { - return -2 - } - - if !DllCall("gdi32\DeleteObject", "UPtr", hbmColor) { - return -3 - } - - return 0 -} - -;##################################################################################### - -PaintDesktop(hdc) -{ - return DllCall("PaintDesktop", "UPtr", hdc) -} - -;##################################################################################### - -CreateCompatibleBitmap(hdc, w, h) -{ - return DllCall("gdi32\CreateCompatibleBitmap", "UPtr", hdc, "Int", w, "Int", h) -} - -;##################################################################################### - -; Function CreateCompatibleDC -; Description This function creates a memory device context (DC) compatible with the specified device -; -; hdc Handle to an existing device context -; -; return returns the handle to a device context or 0 on failure -; -; notes if this handle is 0 (by default), the function creates a memory device context compatible with the application's current screen - -CreateCompatibleDC(hdc:=0) -{ - return DllCall("CreateCompatibleDC", "UPtr", hdc) -} - -;##################################################################################### - -; Function SelectObject -; Description The SelectObject function selects an object into the specified device context (DC). The new object replaces the previous object of the same type -; -; hdc Handle to a DC -; hgdiobj A handle to the object to be selected into the DC -; -; return if the selected object is not a region and the function succeeds, the return value is a handle to the object being replaced -; -; notes The specified object must have been created by using one of the following functions -; Bitmap - CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection (A single bitmap cannot be selected into more than one DC at the same time) -; Brush - CreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrush -; Font - CreateFont, CreateFontIndirect -; Pen - CreatePen, CreatePenIndirect -; Region - CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreateRectRgn, CreateRectRgnIndirect -; -; notes if the selected object is a region and the function succeeds, the return value is one of the following value -; -; SIMPLEREGION = 2 Region consists of a single rectangle -; COMPLEXREGION = 3 Region consists of more than one rectangle -; NULLREGION = 1 Region is empty - -SelectObject(hdc, hgdiobj) -{ - return DllCall("SelectObject", "UPtr", hdc, "UPtr", hgdiobj) -} - -;##################################################################################### - -; Function DeleteObject -; Description This function deletes a logical pen, brush, font, bitmap, region, or palette, freeing all system resources associated with the object -; After the object is deleted, the specified handle is no longer valid -; -; hObject Handle to a logical pen, brush, font, bitmap, region, or palette to delete -; -; return Nonzero indicates success. Zero indicates that the specified handle is not valid or that the handle is currently selected into a device context - -DeleteObject(hObject) -{ - return DllCall("DeleteObject", "UPtr", hObject) -} - -;##################################################################################### - -; Function GetDC -; Description This function retrieves a handle to a display device context (DC) for the client area of the specified window. -; The display device context can be used in subsequent graphics display interface (GDI) functions to draw in the client area of the window. -; -; hwnd Handle to the window whose device context is to be retrieved. If this value is NULL, GetDC retrieves the device context for the entire screen -; -; return The handle the device context for the specified window's client area indicates success. NULL indicates failure - -GetDC(hwnd:=0) -{ - return DllCall("GetDC", "UPtr", hwnd) -} - -;##################################################################################### - -; DCX_CACHE = 0x2 -; DCX_CLIPCHILDREN = 0x8 -; DCX_CLIPSIBLINGS = 0x10 -; DCX_EXCLUDERGN = 0x40 -; DCX_EXCLUDEUPDATE = 0x100 -; DCX_INTERSECTRGN = 0x80 -; DCX_INTERSECTUPDATE = 0x200 -; DCX_LOCKWINDOWUPDATE = 0x400 -; DCX_NORECOMPUTE = 0x100000 -; DCX_NORESETATTRS = 0x4 -; DCX_PARENTCLIP = 0x20 -; DCX_VALIDATE = 0x200000 -; DCX_WINDOW = 0x1 - -GetDCEx(hwnd, flags:=0, hrgnClip:=0) -{ - return DllCall("GetDCEx", "UPtr", hwnd, "UPtr", hrgnClip, "Int", flags) -} - -;##################################################################################### - -; Function ReleaseDC -; Description This function releases a device context (DC), freeing it for use by other applications. The effect of ReleaseDC depends on the type of device context -; -; hdc Handle to the device context to be released -; hwnd Handle to the window whose device context is to be released -; -; return 1 = released -; 0 = not released -; -; notes The application must call the ReleaseDC function for each call to the GetWindowDC function and for each call to the GetDC function that retrieves a common device context -; An application cannot use the ReleaseDC function to release a device context that was created by calling the CreateDC function; instead, it must use the DeleteDC function. - -ReleaseDC(hdc, hwnd:=0) -{ - return DllCall("ReleaseDC", "UPtr", hwnd, "UPtr", hdc) -} - -;##################################################################################### - -; Function DeleteDC -; Description The DeleteDC function deletes the specified device context (DC) -; -; hdc A handle to the device context -; -; return if the function succeeds, the return value is nonzero -; -; notes An application must not delete a DC whose handle was obtained by calling the GetDC function. Instead, it must call the ReleaseDC function to free the DC - -DeleteDC(hdc) -{ - return DllCall("DeleteDC", "UPtr", hdc) -} -;##################################################################################### - -; Function Gdip_LibraryVersion -; Description Get the current library version -; -; return the library version -; -; notes This is useful for non compiled programs to ensure that a person doesn't run an old version when testing your scripts - -Gdip_LibraryVersion() -{ - return 1.45 -} - -;##################################################################################### - -; Function Gdip_LibrarySubVersion -; Description Get the current library sub version -; -; return the library sub version -; -; notes This is the sub-version currently maintained by Rseding91 -; Updated by guest3456 preliminary AHK v2 support -Gdip_LibrarySubVersion() -{ - return 1.54 -} - -;##################################################################################### - -; Function: Gdip_BitmapFromBRA -; Description: Gets a pointer to a gdi+ bitmap from a BRA file -; -; BRAFromMemIn The variable for a BRA file read to memory -; File The name of the file, or its number that you would like (This depends on alternate parameter) -; Alternate Changes whether the File parameter is the file name or its number -; -; return if the function succeeds, the return value is a pointer to a gdi+ bitmap -; -1 = The BRA variable is empty -; -2 = The BRA has an incorrect header -; -3 = The BRA has information missing -; -4 = Could not find file inside the BRA - -Gdip_BitmapFromBRA(BRAFromMemIn, File, Alternate := 0) { - if (!BRAFromMemIn) { - return -1 - } - - Headers := StrSplit(StrGet(BRAFromMemIn.Ptr, 256, "CP0"), "`n") - Header := StrSplit(Headers[1], "|") - HeaderLength := Header.Length - - if (HeaderLength != 4) || (Header[2] != "BRA!") { - return -2 - } - - _Info := StrSplit(Headers[2], "|") - _InfoLength := _Info.Length - - if (_InfoLength != 3) { - return -3 - } - - OffsetTOC := StrPut(Headers[1], "CP0") + StrPut(Headers[2], "CP0") ; + 2 - OffsetData := _Info[2] - SearchIndex := Alternate ? 1 : 2 - TOC := StrGet(BRAFromMemIn.Ptr + OffsetTOC, OffsetData - OffsetTOC - 1, "CP0") - RX1 := "mi`n)^" - Offset := Size := 0 - - if RegExMatch(TOC, RX1 . (Alternate ? File "\|.+?" : "\d+\|" . File) . "\|(\d+)\|(\d+)$", &FileInfo:="") { - Offset := OffsetData + FileInfo[1] - Size := FileInfo[2] - } - - if (Size = 0) { - return -4 - } - - hData := DllCall("GlobalAlloc", "UInt", 2, "UInt", Size, "UPtr") - pData := DllCall("GlobalLock", "Ptr", hData, "UPtr") - DllCall("RtlMoveMemory", "Ptr", pData, "Ptr", BRAFromMemIn.Ptr + Offset, "Ptr", Size) - DllCall("GlobalUnlock", "Ptr", hData) - DllCall("Ole32.dll\CreateStreamOnHGlobal", "Ptr", hData, "Int", 1, "Ptr*", &pStream:=0) - DllCall("Gdiplus.dll\GdipCreateBitmapFromStream", "Ptr", pStream, "Ptr*", &pBitmap:=0) - ObjRelease(pStream) - - return pBitmap -} - -;##################################################################################### - -; Function: Gdip_BitmapFromBase64 -; Description: Creates a bitmap from a Base64 encoded string -; -; Base64 ByRef variable. Base64 encoded string. Immutable, ByRef to avoid performance overhead of passing long strings. -; -; return if the function succeeds, the return value is a pointer to a bitmap, otherwise: -; -1 = Could not calculate the length of the required buffer -; -2 = Could not decode the Base64 encoded string -; -3 = Could not create a memory stream - -Gdip_BitmapFromBase64(&Base64) -{ - ; calculate the length of the buffer needed - if !(DllCall("crypt32\CryptStringToBinary", "UPtr", StrPtr(Base64), "UInt", 0, "UInt", 0x01, "UPtr", 0, "UInt*", &DecLen:=0, "UPtr", 0, "UPtr", 0)) { - return -1 - } - - Dec := Buffer(DecLen, 0) - - ; decode the Base64 encoded string - if !(DllCall("crypt32\CryptStringToBinary", "UPtr", StrPtr(Base64), "UInt", 0, "UInt", 0x01, "UPtr", Dec.Ptr, "UInt*", &DecLen, "UPtr", 0, "UPtr", 0)) { - return -2 - } - - ; create a memory stream - if !(pStream := DllCall("shlwapi\SHCreateMemStream", "UPtr", Dec.Ptr, "UInt", DecLen, "UPtr")) { - return -3 - } - - DllCall("gdiplus\GdipCreateBitmapFromStreamICM", "UPtr", pStream, "Ptr*", &pBitmap:=0) - ObjRelease(pStream) - - return pBitmap -} - -;##################################################################################### - -; Function: Gdip_EncodeBitmapTo64string -; Description: Encode a bitmap to a Base64 encoded string -; -; pBitmap Pointer to a bitmap -; sOutput The name of the file that the bitmap will be saved to. Supported extensions are: .BMP,.DIB,.RLE,.JPG,.JPEG,.JPE,.JFIF,.GIF,.TIF,.TIFF,.PNG -; Quality if saving as jpg (.JPG,.JPEG,.JPE,.JFIF) then quality can be 1-100 with default at maximum quality -; -; return if the function succeeds, the return value is a Base64 encoded string of the pBitmap - -Gdip_EncodeBitmapTo64string(pBitmap, extension := "png", quality := "") { - - ; Fill a buffer with the available image codec info. - DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &count:=0, "uint*", &size:=0) - DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", ci := Buffer(size)) - - ; struct ImageCodecInfo - http://www.jose.it-berater.org/gdiplus/reference/structures/imagecodecinfo.htm - loop { - if (A_Index > count) - throw Error("Could not find a matching encoder for the specified file format.") - - idx := (48+7*A_PtrSize)*(A_Index-1) - } until InStr(StrGet(NumGet(ci, idx+32+3*A_PtrSize, "ptr"), "UTF-16"), extension) ; FilenameExtension - - ; Get the pointer to the clsid of the matching encoder. - pCodec := ci.ptr + idx ; ClassID - - ; JPEG default quality is 75. Otherwise set a quality value from [0-100]. - if (quality ~= "^-?\d+$") and ("image/jpeg" = StrGet(NumGet(ci, idx+32+4*A_PtrSize, "ptr"), "UTF-16")) { ; MimeType - ; Use a separate buffer to store the quality as ValueTypeLong (4). - v := Buffer(4) - NumPut("uint", quality, v) - - ; struct EncoderParameter - http://www.jose.it-berater.org/gdiplus/reference/structures/encoderparameter.htm - ; enum ValueType - https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparametervaluetype - ; clsid Image Encoder Constants - http://www.jose.it-berater.org/gdiplus/reference/constants/gdipimageencoderconstants.htm - ep := Buffer(24+2*A_PtrSize) ; sizeof(EncoderParameter) = ptr + n*(28, 32) - NumPut( "uptr", 1, ep, 0) ; Count - DllCall("ole32\CLSIDFromString", "wstr", "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}", "ptr", ep.ptr+A_PtrSize, "HRESULT") - NumPut( "uint", 1, ep, 16+A_PtrSize) ; Number of Values - NumPut( "uint", 4, ep, 20+A_PtrSize) ; Type - NumPut( "ptr", v.ptr, ep, 24+A_PtrSize) ; Value - } - - ; Create a Stream. - DllCall("ole32\CreateStreamOnHGlobal", "ptr", 0, "int", True, "ptr*", &pStream:=0, "HRESULT") - DllCall("gdiplus\GdipSaveImageToStream", "ptr", pBitmap, "ptr", pStream, "ptr", pCodec, "ptr", IsSet(ep) ? ep : 0) - - ; Get a pointer to binary data. - DllCall("ole32\GetHGlobalFromStream", "ptr", pStream, "ptr*", &hbin:=0, "HRESULT") - bin := DllCall("GlobalLock", "ptr", hbin, "ptr") - size := DllCall("GlobalSize", "uint", bin, "uptr") - - ; Calculate the length of the base64 string. - flags := 0x40000001 ; CRYPT_STRING_NOCRLF | CRYPT_STRING_BASE64 - length := 4 * Ceil(size/3) + 1 ; An extra byte of padding is required. - str := Buffer(length) - - ; Using CryptBinaryToStringA saves about 2MB in memory. - DllCall("crypt32\CryptBinaryToStringA", "ptr", bin, "uint", size, "uint", flags, "ptr", str, "uint*", &length) - - ; Release binary data and stream. - DllCall("GlobalUnlock", "ptr", hbin) - ObjRelease(pStream) - - ; Return encoded string length minus 1. - return StrGet(str, length, "CP0") -} - -;##################################################################################### - -; Function Gdip_DrawRectangle -; Description This function uses a pen to draw the outline of a rectangle into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x x-coordinate of the top left of the rectangle -; y y-coordinate of the top left of the rectangle -; w width of the rectanlge -; h height of the rectangle -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h) -{ - return DllCall("gdiplus\GdipDrawRectangle", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h) -} - -;##################################################################################### - -; Function Gdip_DrawRoundedRectangle -; Description This function uses a pen to draw the outline of a rounded rectangle into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x x-coordinate of the top left of the rounded rectangle -; y y-coordinate of the top left of the rounded rectangle -; w width of the rectanlge -; h height of the rectangle -; r radius of the rounded corners -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawRoundedRectangle(pGraphics, pPen, x, y, w, h, r) -{ - Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4) - _E := Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h) - Gdip_ResetClip(pGraphics) - Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4) - Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4) - Gdip_DrawEllipse(pGraphics, pPen, x, y, 2*r, 2*r) - Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y, 2*r, 2*r) - Gdip_DrawEllipse(pGraphics, pPen, x, y+h-(2*r), 2*r, 2*r) - Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y+h-(2*r), 2*r, 2*r) - Gdip_ResetClip(pGraphics) - return _E -} - -;##################################################################################### - -; Function Gdip_DrawEllipse -; Description This function uses a pen to draw the outline of an ellipse into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x x-coordinate of the top left of the rectangle the ellipse will be drawn into -; y y-coordinate of the top left of the rectangle the ellipse will be drawn into -; w width of the ellipse -; h height of the ellipse -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawEllipse(pGraphics, pPen, x, y, w, h) -{ - return DllCall("gdiplus\GdipDrawEllipse", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h) -} - -;##################################################################################### - -; Function Gdip_DrawBezier -; Description This function uses a pen to draw the outline of a bezier (a weighted curve) into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x1 x-coordinate of the start of the bezier -; y1 y-coordinate of the start of the bezier -; x2 x-coordinate of the first arc of the bezier -; y2 y-coordinate of the first arc of the bezier -; x3 x-coordinate of the second arc of the bezier -; y3 y-coordinate of the second arc of the bezier -; x4 x-coordinate of the end of the bezier -; y4 y-coordinate of the end of the bezier -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawBezier(pGraphics, pPen, x1, y1, x2, y2, x3, y3, x4, y4) -{ - return DllCall("gdiplus\GdipDrawBezier" - , "UPtr", pgraphics - , "UPtr", pPen - , "Float", x1 - , "Float", y1 - , "Float", x2 - , "Float", y2 - , "Float", x3 - , "Float", y3 - , "Float", x4 - , "Float", y4) -} - -;##################################################################################### - -; Function Gdip_DrawArc -; Description This function uses a pen to draw the outline of an arc into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x x-coordinate of the start of the arc -; y y-coordinate of the start of the arc -; w width of the arc -; h height of the arc -; StartAngle specifies the angle between the x-axis and the starting point of the arc -; SweepAngle specifies the angle between the starting and ending points of the arc -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawArc(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle) -{ - return DllCall("gdiplus\GdipDrawArc" - , "UPtr", pGraphics - , "UPtr", pPen - , "Float", x - , "Float", y - , "Float", w - , "Float", h - , "Float", StartAngle - , "Float", SweepAngle) -} - -;##################################################################################### - -; Function Gdip_DrawPie -; Description This function uses a pen to draw the outline of a pie into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x x-coordinate of the start of the pie -; y y-coordinate of the start of the pie -; w width of the pie -; h height of the pie -; StartAngle specifies the angle between the x-axis and the starting point of the pie -; SweepAngle specifies the angle between the starting and ending points of the pie -; -; return status enumeration. 0 = success -; -; notes as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width - -Gdip_DrawPie(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle) -{ - return DllCall("gdiplus\GdipDrawPie", "UPtr", pGraphics, "UPtr", pPen, "Float", x, "Float", y, "Float", w, "Float", h, "Float", StartAngle, "Float", SweepAngle) -} - -;##################################################################################### - -; Function Gdip_DrawLine -; Description This function uses a pen to draw a line into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; x1 x-coordinate of the start of the line -; y1 y-coordinate of the start of the line -; x2 x-coordinate of the end of the line -; y2 y-coordinate of the end of the line -; -; return status enumeration. 0 = success - -Gdip_DrawLine(pGraphics, pPen, x1, y1, x2, y2) -{ - return DllCall("gdiplus\GdipDrawLine" - , "UPtr", pGraphics - , "UPtr", pPen - , "Float", x1 - , "Float", y1 - , "Float", x2 - , "Float", y2) -} - -;##################################################################################### - -; Function Gdip_DrawLines -; Description This function uses a pen to draw a series of joined lines into the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pPen Pointer to a pen -; Points the coordinates of all the points passed as x1,y1|x2,y2|x3,y3..... -; -; return status enumeration. 0 = success - -Gdip_DrawLines(pGraphics, pPen, points) -{ - points := StrSplit(points, "|") - pointF := Buffer(8*points.Length) - pointsLength := 0 - for point in points { - coords := StrSplit(point, ",") - if (coords.Length != 2) { - if (coords.Length > 0) { - MsgBox("Skipping wrong points of length " coords.Length) - } - continue - } - NumPut("Float", coords[1], pointF, 8*(A_Index-1)) - NumPut("Float", coords[2], pointF, (8*(A_Index-1))+4) - pointsLength += 1 - } - return DllCall("gdiplus\GdipDrawLines", "UPtr", pGraphics, "UPtr", pPen, "UPtr", pointF.Ptr, "Int", pointsLength) -} - -;##################################################################################### - -; Function Gdip_FillRectangle -; Description This function uses a brush to fill a rectangle in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; x x-coordinate of the top left of the rectangle -; y y-coordinate of the top left of the rectangle -; w width of the rectanlge -; h height of the rectangle -; -; return status enumeration. 0 = success - -Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) -{ - return DllCall("gdiplus\GdipFillRectangle" - , "UPtr", pGraphics - , "UPtr", pBrush - , "Float", x - , "Float", y - , "Float", w - , "Float", h) -} - -;##################################################################################### - -; Function Gdip_FillRoundedRectangle -; Description This function uses a brush to fill a rounded rectangle in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; x x-coordinate of the top left of the rounded rectangle -; y y-coordinate of the top left of the rounded rectangle -; w width of the rectanlge -; h height of the rectangle -; r radius of the rounded corners -; -; return status enumeration. 0 = success - -Gdip_FillRoundedRectangle(pGraphics, pBrush, x, y, w, h, r) -{ - Region := Gdip_GetClipRegion(pGraphics) - Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4) - Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4) - _E := Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) - Gdip_SetClipRegion(pGraphics, Region, 0) - Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4) - Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4) - Gdip_FillEllipse(pGraphics, pBrush, x, y, 2*r, 2*r) - Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y, 2*r, 2*r) - Gdip_FillEllipse(pGraphics, pBrush, x, y+h-(2*r), 2*r, 2*r) - Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y+h-(2*r), 2*r, 2*r) - Gdip_SetClipRegion(pGraphics, Region, 0) - Gdip_DeleteRegion(Region) - return _E -} - -;##################################################################################### - -; Function Gdip_FillPolygon -; Description This function uses a brush to fill a polygon in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; Points the coordinates of all the points passed as x1,y1|x2,y2|x3,y3..... -; -; return status enumeration. 0 = success -; -; notes Alternate will fill the polygon as a whole, wheras winding will fill each new "segment" -; Alternate = 0 -; Winding = 1 - -Gdip_FillPolygon(pGraphics, pBrush, Points, FillMode:=0) -{ - Points := StrSplit(Points, "|") - PointsLength := Points.Length - PointF := Buffer(8*PointsLength) - For eachPoint, Point in Points - { - Coord := StrSplit(Point, ",") - NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) - NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) - } - return DllCall("gdiplus\GdipFillPolygon", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", PointF.Ptr, "Int", PointsLength, "Int", FillMode) -} - -;##################################################################################### - -; Function Gdip_FillPie -; Description This function uses a brush to fill a pie in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; x x-coordinate of the top left of the pie -; y y-coordinate of the top left of the pie -; w width of the pie -; h height of the pie -; StartAngle specifies the angle between the x-axis and the starting point of the pie -; SweepAngle specifies the angle between the starting and ending points of the pie -; -; return status enumeration. 0 = success - -Gdip_FillPie(pGraphics, pBrush, x, y, w, h, StartAngle, SweepAngle) -{ - return DllCall("gdiplus\GdipFillPie" - , "UPtr", pGraphics - , "UPtr", pBrush - , "Float", x - , "Float", y - , "Float", w - , "Float", h - , "Float", StartAngle - , "Float", SweepAngle) -} - -;##################################################################################### - -; Function Gdip_FillEllipse -; Description This function uses a brush to fill an ellipse in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; x x-coordinate of the top left of the ellipse -; y y-coordinate of the top left of the ellipse -; w width of the ellipse -; h height of the ellipse -; -; return status enumeration. 0 = success - -Gdip_FillEllipse(pGraphics, pBrush, x, y, w, h) -{ - return DllCall("gdiplus\GdipFillEllipse", "UPtr", pGraphics, "UPtr", pBrush, "Float", x, "Float", y, "Float", w, "Float", h) -} - -;##################################################################################### - -; Function Gdip_FillRegion -; Description This function uses a brush to fill a region in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; Region Pointer to a Region -; -; return status enumeration. 0 = success -; -; notes You can create a region Gdip_CreateRegion() and then add to this - -Gdip_FillRegion(pGraphics, pBrush, Region) -{ - return DllCall("gdiplus\GdipFillRegion", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", Region) -} - -;##################################################################################### - -; Function Gdip_FillPath -; Description This function uses a brush to fill a path in the Graphics of a bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBrush Pointer to a brush -; Region Pointer to a Path -; -; return status enumeration. 0 = success - -Gdip_FillPath(pGraphics, pBrush, pPath) -{ - return DllCall("gdiplus\GdipFillPath", "UPtr", pGraphics, "UPtr", pBrush, "UPtr", pPath) -} - -;##################################################################################### - -; Function Gdip_DrawImagePointsRect -; Description This function draws a bitmap into the Graphics of another bitmap and skews it -; -; pGraphics Pointer to the Graphics of a bitmap -; pBitmap Pointer to a bitmap to be drawn -; Points Points passed as x1,y1|x2,y2|x3,y3 (3 points: top left, top right, bottom left) describing the drawing of the bitmap -; sx x-coordinate of source upper-left corner -; sy y-coordinate of source upper-left corner -; sw width of source rectangle -; sh height of source rectangle -; Matrix a matrix used to alter image attributes when drawing -; -; return status enumeration. 0 = success -; -; notes if sx,sy,sw,sh are missed then the entire source bitmap will be used -; Matrix can be omitted to just draw with no alteration to ARGB -; Matrix may be passed as a digit from 0 - 1 to change just transparency -; Matrix can be passed as a matrix with any delimiter - -Gdip_DrawImagePointsRect(pGraphics, pBitmap, Points, sx:="", sy:="", sw:="", sh:="", Matrix:=1) -{ - Points := StrSplit(Points, "|") - PointsLength := Points.Length - PointF := Buffer(8*PointsLength) - For eachPoint, Point in Points - { - Coord := StrSplit(Point, ",") - NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) - NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) - } - - if !IsNumber(Matrix) - ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix) - else if (Matrix != 1) - ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1") - else - ImageAttr := 0 - - if (sx = "" && sy = "" && sw = "" && sh = "") - { - sx := 0, sy := 0 - sw := Gdip_GetImageWidth(pBitmap) - sh := Gdip_GetImageHeight(pBitmap) - } - - _E := DllCall("gdiplus\GdipDrawImagePointsRect" - , "UPtr", pGraphics - , "UPtr", pBitmap - , "UPtr", PointF.Ptr - , "Int", PointsLength - , "Float", sx - , "Float", sy - , "Float", sw - , "Float", sh - , "Int", 2 - , "UPtr", ImageAttr - , "UPtr", 0 - , "UPtr", 0) - if ImageAttr - Gdip_DisposeImageAttributes(ImageAttr) - return _E -} - -;##################################################################################### - -; Function Gdip_DrawImage -; Description This function draws a bitmap into the Graphics of another bitmap -; -; pGraphics Pointer to the Graphics of a bitmap -; pBitmap Pointer to a bitmap to be drawn -; dx x-coord of destination upper-left corner -; dy y-coord of destination upper-left corner -; dw width of destination image -; dh height of destination image -; sx x-coordinate of source upper-left corner -; sy y-coordinate of source upper-left corner -; sw width of source image -; sh height of source image -; Matrix a matrix used to alter image attributes when drawing -; -; return status enumeration. 0 = success -; -; notes if sx,sy,sw,sh are missed then the entire source bitmap will be used -; Gdip_DrawImage performs faster -; Matrix can be omitted to just draw with no alteration to ARGB -; Matrix may be passed as a digit from 0 - 1 to change just transparency -; Matrix can be passed as a matrix with any delimiter. For example: -; MatrixBright= -; ( -; 1.5 |0 |0 |0 |0 -; 0 |1.5 |0 |0 |0 -; 0 |0 |1.5 |0 |0 -; 0 |0 |0 |1 |0 -; 0.05 |0.05 |0.05 |0 |1 -; ) -; -; notes MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1 -; MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1 -; MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|1|1|1|0|1 - -Gdip_DrawImage(pGraphics, pBitmap, dx:="", dy:="", dw:="", dh:="", sx:="", sy:="", sw:="", sh:="", Matrix:=1) -{ - if !IsNumber(Matrix) - ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix) - else if (Matrix != 1) - ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1") - else - ImageAttr := 0 - - if (sx = "" && sy = "" && sw = "" && sh = "") - { - if (dx = "" && dy = "" && dw = "" && dh = "") - { - sx := dx := 0, sy := dy := 0 - sw := dw := Gdip_GetImageWidth(pBitmap) - sh := dh := Gdip_GetImageHeight(pBitmap) - } - else - { - sx := sy := 0 - sw := Gdip_GetImageWidth(pBitmap) - sh := Gdip_GetImageHeight(pBitmap) - } - } - - _E := DllCall("gdiplus\GdipDrawImageRectRect" - , "UPtr", pGraphics - , "UPtr", pBitmap - , "Float", dx - , "Float", dy - , "Float", dw - , "Float", dh - , "Float", sx - , "Float", sy - , "Float", sw - , "Float", sh - , "Int", 2 - , "UPtr", ImageAttr - , "UPtr", 0 - , "UPtr", 0) - if ImageAttr - Gdip_DisposeImageAttributes(ImageAttr) - return _E -} - -;##################################################################################### - -; Function Gdip_SetImageAttributesColorMatrix -; Description This function creates an image matrix ready for drawing -; -; Matrix a matrix used to alter image attributes when drawing -; passed with any delimeter -; -; return returns an image matrix on sucess or 0 if it fails -; -; notes MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1 -; MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1 -; MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|1|1|1|0|1 - -Gdip_SetImageAttributesColorMatrix(Matrix) -{ - ColourMatrix := Buffer(100, 0) - Matrix := RegExReplace(RegExReplace(Matrix, "^[^\d-\.]+([\d\.])", "$1", , 1), "[^\d-\.]+", "|") - Matrix := StrSplit(Matrix, "|") - - loop 25 { - M := (Matrix[A_Index] != "") ? Matrix[A_Index] : Mod(A_Index-1, 6) ? 0 : 1 - NumPut("Float", M, ColourMatrix, (A_Index-1)*4) - } - - DllCall("gdiplus\GdipCreateImageAttributes", "UPtr*", &ImageAttr:=0) - DllCall("gdiplus\GdipSetImageAttributesColorMatrix", "UPtr", ImageAttr, "Int", 1, "Int", 1, "UPtr", ColourMatrix.Ptr, "UPtr", 0, "Int", 0) - - return ImageAttr -} - -;##################################################################################### - -; Function Gdip_GraphicsFromImage -; Description This function gets the graphics for a bitmap used for drawing functions -; -; pBitmap Pointer to a bitmap to get the pointer to its graphics -; -; return returns a pointer to the graphics of a bitmap -; -; notes a bitmap can be drawn into the graphics of another bitmap - -Gdip_GraphicsFromImage(pBitmap) -{ - DllCall("gdiplus\GdipGetImageGraphicsContext", "UPtr", pBitmap, "UPtr*", &pGraphics:=0) - return pGraphics -} - -;##################################################################################### - -; Function Gdip_GraphicsFromHDC -; Description This function gets the graphics from the handle to a device context -; -; hdc This is the handle to the device context -; -; return returns a pointer to the graphics of a bitmap -; -; notes You can draw a bitmap into the graphics of another bitmap - -Gdip_GraphicsFromHDC(hdc) -{ - DllCall("gdiplus\GdipCreateFromHDC", "UPtr", hdc, "UPtr*", &pGraphics:=0) - return pGraphics -} - -;##################################################################################### - -; Function Gdip_GetDC -; Description This function gets the device context of the passed Graphics -; -; hdc This is the handle to the device context -; -; return returns the device context for the graphics of a bitmap - -Gdip_GetDC(pGraphics) -{ - DllCall("gdiplus\GdipGetDC", "UPtr", pGraphics, "UPtr*", &hdc:=0) - return hdc -} - -;##################################################################################### - -; Function Gdip_ReleaseDC -; Description This function releases a device context from use for further use -; -; pGraphics Pointer to the graphics of a bitmap -; hdc This is the handle to the device context -; -; return status enumeration. 0 = success - -Gdip_ReleaseDC(pGraphics, hdc) -{ - return DllCall("gdiplus\GdipReleaseDC", "UPtr", pGraphics, "UPtr", hdc) -} - -;##################################################################################### - -; Function Gdip_GraphicsClear -; Description Clears the graphics of a bitmap ready for further drawing -; -; pGraphics Pointer to the graphics of a bitmap -; ARGB The colour to clear the graphics to -; -; return status enumeration. 0 = success -; -; notes By default this will make the background invisible -; Using clipping regions you can clear a particular area on the graphics rather than clearing the entire graphics - -Gdip_GraphicsClear(pGraphics, ARGB:=0x00ffffff) -{ - return DllCall("gdiplus\GdipGraphicsClear", "UPtr", pGraphics, "Int", ARGB) -} - -;##################################################################################### - -; Function Gdip_BlurBitmap -; Description Gives a pointer to a blurred bitmap from a pointer to a bitmap -; -; pBitmap Pointer to a bitmap to be blurred -; Blur The Amount to blur a bitmap by from 1 (least blur) to 100 (most blur) -; -; return if the function succeeds, the return value is a pointer to the new blurred bitmap -; -1 = The blur parameter is outside the range 1-100 -; -; notes This function will not dispose of the original bitmap - -Gdip_BlurBitmap(pBitmap, Blur) -{ - if (Blur > 100 || Blur < 1) { - return -1 - } - - sWidth := Gdip_GetImageWidth(pBitmap), sHeight := Gdip_GetImageHeight(pBitmap) - dWidth := sWidth//Blur, dHeight := sHeight//Blur - - pBitmap1 := Gdip_CreateBitmap(dWidth, dHeight) - G1 := Gdip_GraphicsFromImage(pBitmap1) - Gdip_SetInterpolationMode(G1, 7) - Gdip_DrawImage(G1, pBitmap, 0, 0, dWidth, dHeight, 0, 0, sWidth, sHeight) - - Gdip_DeleteGraphics(G1) - - pBitmap2 := Gdip_CreateBitmap(sWidth, sHeight) - G2 := Gdip_GraphicsFromImage(pBitmap2) - Gdip_SetInterpolationMode(G2, 7) - Gdip_DrawImage(G2, pBitmap1, 0, 0, sWidth, sHeight, 0, 0, dWidth, dHeight) - - Gdip_DeleteGraphics(G2) - Gdip_DisposeImage(pBitmap1) - - return pBitmap2 -} - -;##################################################################################### - -; Function: Gdip_SaveBitmapToFile -; Description: Saves a bitmap to a file in any supported format onto disk -; -; pBitmap Pointer to a bitmap -; sOutput The name of the file that the bitmap will be saved to. Supported extensions are: .BMP,.DIB,.RLE,.JPG,.JPEG,.JPE,.JFIF,.GIF,.TIF,.TIFF,.PNG -; Quality if saving as jpg (.JPG,.JPEG,.JPE,.JFIF) then quality can be 1-100 with default at maximum quality -; -; return if the function succeeds, the return value is zero, otherwise: -; -1 = Extension supplied is not a supported file format -; -2 = Could not get a list of encoders on system -; -3 = Could not find matching encoder for specified file format -; -4 = Could not get WideChar name of output file -; -5 = Could not save file to disk -; -; notes This function will use the extension supplied from the sOutput parameter to determine the output format - -Gdip_SaveBitmapToFile(pBitmap, sOutput, Quality:=75) -{ - _p := 0 - - SplitPath sOutput,,, &extension:="" - if (!RegExMatch(extension, "^(?i:BMP|DIB|RLE|JPG|JPEG|JPE|JFIF|GIF|TIF|TIFF|PNG)$")) { - return -1 - } - extension := "." extension - - DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &nCount:=0, "uint*", &nSize:=0) - ci := Buffer(nSize) - DllCall("gdiplus\GdipGetImageEncoders", "UInt", nCount, "UInt", nSize, "UPtr", ci.Ptr) - if !(nCount && nSize) { - return -2 - } - - loop nCount { - address := NumGet(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "UPtr") - sString := StrGet(address, "UTF-16") - if !InStr(sString, "*" extension) - continue - - pCodec := ci.Ptr+idx - break - } - - if !pCodec { - return -3 - } - - if (Quality != 75) { - Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality - - if RegExMatch(extension, "^\.(?i:JPG|JPEG|JPE|JFIF)$") { - DllCall("gdiplus\GdipGetEncoderParameterListSize", "UPtr", pBitmap, "UPtr", pCodec, "uint*", &nSize) - EncoderParameters := Buffer(nSize, 0) - DllCall("gdiplus\GdipGetEncoderParameterList", "UPtr", pBitmap, "UPtr", pCodec, "UInt", nSize, "UPtr", EncoderParameters.Ptr) - nCount := NumGet(EncoderParameters, "UInt") - loop nCount - { - elem := (24+(A_PtrSize ? A_PtrSize : 4))*(A_Index-1) + 4 + (pad := A_PtrSize = 8 ? 4 : 0) - if (NumGet(EncoderParameters, elem+16, "UInt") = 1) && (NumGet(EncoderParameters, elem+20, "UInt") = 6) - { - _p := elem + EncoderParameters.Ptr - pad - 4 - NumPut("UInt", Quality, NumGet(NumPut("UInt", 4, NumPut("UInt", 1, _p+0)+20), "UInt")) - break - } - } - } - } - - _E := DllCall("gdiplus\GdipSaveImageToFile", "UPtr", pBitmap, "UPtr", StrPtr(sOutput), "UPtr", pCodec, "UInt", _p ? _p : 0) - - return _E ? -5 : 0 -} - -;##################################################################################### - -; Function Gdip_GetPixel -; Description Gets the ARGB of a pixel in a bitmap -; -; pBitmap Pointer to a bitmap -; x x-coordinate of the pixel -; y y-coordinate of the pixel -; -; return Returns the ARGB value of the pixel - -Gdip_GetPixel(pBitmap, x, y) -{ - DllCall("gdiplus\GdipBitmapGetPixel", "UPtr", pBitmap, "Int", x, "Int", y, "uint*", &ARGB:=0) - return ARGB -} - -;##################################################################################### - -; Function Gdip_SetPixel -; Description Sets the ARGB of a pixel in a bitmap -; -; pBitmap Pointer to a bitmap -; x x-coordinate of the pixel -; y y-coordinate of the pixel -; -; return status enumeration. 0 = success - -Gdip_SetPixel(pBitmap, x, y, ARGB) -{ - return DllCall("gdiplus\GdipBitmapSetPixel", "UPtr", pBitmap, "Int", x, "Int", y, "Int", ARGB) -} - -;##################################################################################### - -; Function Gdip_GetImageWidth -; Description Gives the width of a bitmap -; -; pBitmap Pointer to a bitmap -; -; return Returns the width in pixels of the supplied bitmap - -Gdip_GetImageWidth(pBitmap) -{ - DllCall("gdiplus\GdipGetImageWidth", "UPtr", pBitmap, "uint*", &Width:=0) - return Width -} - -;##################################################################################### - -; Function Gdip_GetImageHeight -; Description Gives the height of a bitmap -; -; pBitmap Pointer to a bitmap -; -; return Returns the height in pixels of the supplied bitmap - -Gdip_GetImageHeight(pBitmap) -{ - DllCall("gdiplus\GdipGetImageHeight", "UPtr", pBitmap, "uint*", &Height:=0) - return Height -} - -;##################################################################################### - -; Function Gdip_GetDimensions -; Description Gives the width and height of a bitmap -; -; pBitmap Pointer to a bitmap -; Width ByRef variable. This variable will be set to the width of the bitmap -; Height ByRef variable. This variable will be set to the height of the bitmap -; -; return No return value -; Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height - -Gdip_GetImageDimensions(pBitmap, &Width, &Height) -{ - DllCall("gdiplus\GdipGetImageWidth", "UPtr", pBitmap, "uint*", &Width:=0) - DllCall("gdiplus\GdipGetImageHeight", "UPtr", pBitmap, "uint*", &Height:=0) -} - -;##################################################################################### - -Gdip_GetDimensions(pBitmap, &Width, &Height) -{ - Gdip_GetImageDimensions(pBitmap, &Width, &Height) -} - -;##################################################################################### - -Gdip_GetImagePixelFormat(pBitmap) -{ - DllCall("gdiplus\GdipGetImagePixelFormat", "UPtr", pBitmap, "UPtr*", &_Format:=0) - return _Format -} - -;##################################################################################### - -; Function Gdip_GetDpiX -; Description Gives the horizontal dots per inch of the graphics of a bitmap -; -; pBitmap Pointer to a bitmap -; Width ByRef variable. This variable will be set to the width of the bitmap -; Height ByRef variable. This variable will be set to the height of the bitmap -; -; return No return value -; Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height - -Gdip_GetDpiX(pGraphics) -{ - DllCall("gdiplus\GdipGetDpiX", "UPtr", pGraphics, "float*", &dpix:=0) - return Round(dpix) -} - -;##################################################################################### - -Gdip_GetDpiY(pGraphics) -{ - DllCall("gdiplus\GdipGetDpiY", "UPtr", pGraphics, "float*", &dpiy:=0) - return Round(dpiy) -} - -;##################################################################################### - -Gdip_GetImageHorizontalResolution(pBitmap) -{ - DllCall("gdiplus\GdipGetImageHorizontalResolution", "UPtr", pBitmap, "float*", &dpix:=0) - return Round(dpix) -} - -;##################################################################################### - -Gdip_GetImageVerticalResolution(pBitmap) -{ - DllCall("gdiplus\GdipGetImageVerticalResolution", "UPtr", pBitmap, "float*", &dpiy:=0) - return Round(dpiy) -} - -;##################################################################################### - -Gdip_BitmapSetResolution(pBitmap, dpix, dpiy) -{ - return DllCall("gdiplus\GdipBitmapSetResolution", "UPtr", pBitmap, "Float", dpix, "Float", dpiy) -} - -;##################################################################################### - -Gdip_CreateBitmapFromFile(sFile, IconNumber:=1, IconSize:="") -{ - SplitPath sFile,,, &extension:="" - if RegExMatch(extension, "^(?i:exe|dll)$") { - Sizes := IconSize ? IconSize : 256 "|" 128 "|" 64 "|" 48 "|" 32 "|" 16 - BufSize := 16 + (2*(A_PtrSize ? A_PtrSize : 4)) - - buf := Buffer(BufSize, 0) - hIcon := 0 - - for eachSize, Size in StrSplit( Sizes, "|" ) { - DllCall("PrivateExtractIcons", "str", sFile, "Int", IconNumber-1, "Int", Size, "Int", Size, "UPtr*", &hIcon, "UPtr*", 0, "UInt", 1, "UInt", 0) - - if (!hIcon) { - continue - } - - if !DllCall("GetIconInfo", "UPtr", hIcon, "UPtr", buf.Ptr) { - DestroyIcon(hIcon) - continue - } - - hbmMask := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4)) - hbmColor := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4) + (A_PtrSize ? A_PtrSize : 4)) - if !(hbmColor && DllCall("GetObject", "UPtr", hbmColor, "Int", BufSize, "UPtr", buf.Ptr)) - { - DestroyIcon(hIcon) - continue - } - break - } - - if (!hIcon) { - return -1 - } - - Width := NumGet(buf, 4, "Int"), Height := NumGet(buf, 8, "Int") - hbm := CreateDIBSection(Width, -Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm) - if !DllCall("DrawIconEx", "UPtr", hdc, "Int", 0, "Int", 0, "UPtr", hIcon, "UInt", Width, "UInt", Height, "UInt", 0, "UPtr", 0, "UInt", 3) { - DestroyIcon(hIcon) - return -2 - } - - dib := Buffer(104) - DllCall("GetObject", "UPtr", hbm, "Int", A_PtrSize = 8 ? 104 : 84, "UPtr", dib.Ptr) ; sizeof(DIBSECTION) = 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize - Stride := NumGet(dib, 12, "Int"), Bits := NumGet(dib, 20 + (A_PtrSize = 8 ? 4 : 0)) ; padding - DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", Width, "Int", Height, "Int", Stride, "Int", 0x26200A, "UPtr", Bits, "UPtr*", &pBitmapOld:=0) - pBitmap := Gdip_CreateBitmap(Width, Height) - _G := Gdip_GraphicsFromImage(pBitmap) - , Gdip_DrawImage(_G, pBitmapOld, 0, 0, Width, Height, 0, 0, Width, Height) - SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc) - Gdip_DeleteGraphics(_G), Gdip_DisposeImage(pBitmapOld) - DestroyIcon(hIcon) - - } else { - DllCall("gdiplus\GdipCreateBitmapFromFile", "UPtr", StrPtr(sFile), "UPtr*", &pBitmap:=0) - } - - return pBitmap -} - -;##################################################################################### - -Gdip_CreateBitmapFromHBITMAP(hBitmap, Palette:=0) -{ - DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "UPtr", hBitmap, "UPtr", Palette, "UPtr*", &pBitmap:=0) - return pBitmap -} - -;##################################################################################### - -Gdip_CreateHBITMAPFromBitmap(pBitmap, Background:=0xffffffff) -{ - DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "UPtr", pBitmap, "UPtr*", &hbm:=0, "Int", Background) - return hbm -} - -;##################################################################################### - -Gdip_CreateARGBBitmapFromHBITMAP(&hBitmap) { - ; struct BITMAP - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmap - dib := Buffer(76+2*(A_PtrSize=8?4:0)+2*A_PtrSize) - DllCall("GetObject" - , "ptr", hBitmap - , "Int", dib.Size - , "ptr", dib.Ptr) ; sizeof(DIBSECTION) = 84, 104 - , width := NumGet(dib, 4, "UInt") - , height := NumGet(dib, 8, "UInt") - , bpp := NumGet(dib, 18, "ushort") - - ; Fallback to built-in method if pixels are not 32-bit ARGB. - if (bpp != 32) { ; This built-in version is 120% faster but ignores transparency. - DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "ptr", hBitmap, "ptr", 0, "ptr*", &pBitmap:=0) - return pBitmap - } - - ; Create a handle to a device context and associate the image. - hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr") ; Creates a memory DC compatible with the current screen. - obm := DllCall("SelectObject", "ptr", hdc, "ptr", hBitmap, "ptr") ; Put the (hBitmap) image onto the device context. - - ; Create a device independent bitmap with negative height. All DIBs use the screen pixel format (pARGB). - ; Use hbm to buffer the image such that top-down and bottom-up images are mapped to this top-down buffer. - cdc := DllCall("CreateCompatibleDC", "ptr", hdc, "ptr") - bi := Buffer(40, 0) ; sizeof(bi) = 40 - NumPut( - "UInt", 40, ; Size - "UInt", width, ; Width - "Int", height, ; Height - Negative so (0, 0) is top-left. - "ushort", 1, ; Planes - "ushort", 32, ; BitCount / BitsPerPixel - bi) - hbm := DllCall("CreateDIBSection", "ptr", cdc, "ptr", bi.Ptr, "UInt", 0 - , "ptr*", &pBits:=0 ; pBits is the pointer to (top-down) pixel values. - , "ptr", 0, "UInt", 0, "ptr") - ob2 := DllCall("SelectObject", "ptr", cdc, "ptr", hbm, "ptr") - - ; This is the 32-bit ARGB pBitmap (different from an hBitmap) that will receive the final converted pixels. - DllCall("gdiplus\GdipCreateBitmapFromScan0" - , "Int", width, "Int", height, "Int", 0, "Int", 0x26200A, "ptr", 0, "ptr*", &pBitmap:=0) - - ; Create a Scan0 buffer pointing to pBits. The buffer has pixel format pARGB. - Rect := Buffer(16, 0) ; sizeof(Rect) = 16 - NumPut( - "UInt", width, ; Width - "UInt", height, ; Height - Rect, 8) - - BitmapData := Buffer(16+2*A_PtrSize, 0) ; sizeof(BitmapData) = 24, 32 - NumPut( - "UInt", width, ; Width - "UInt", height, ; Height - "Int", 4 * width, ; Stride - "Int", 0xE200B, ; PixelFormat - "ptr", pBits, ; Scan0 - BitmapData) - - ; Use LockBits to create a writable buffer that converts pARGB to ARGB. - DllCall("gdiplus\GdipBitmapLockBits" - , "ptr", pBitmap - , "ptr", Rect.Ptr - , "UInt", 6 ; ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly - , "Int", 0xE200B ; Format32bppPArgb - , "ptr", BitmapData.Ptr) ; Contains the pointer (pBits) to the hbm. - - ; Copies the image (hBitmap) to a top-down bitmap. Removes bottom-up-ness if present. - DllCall("gdi32\BitBlt" - , "ptr", cdc, "Int", 0, "Int", 0, "Int", width, "Int", height - , "ptr", hdc, "Int", 0, "Int", 0, "UInt", 0x00CC0020) ; SRCCOPY - - ; Convert the pARGB pixels copied into the device independent bitmap (hbm) to ARGB. - DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", BitmapData.Ptr) - - ; Cleanup the buffer and device contexts. - DllCall("SelectObject", "ptr", cdc, "ptr", ob2) - DllCall("DeleteObject", "ptr", hbm) - DllCall("DeleteDC", "ptr", cdc) - DllCall("SelectObject", "ptr", hdc, "ptr", obm) - DllCall("DeleteDC", "ptr", hdc) - - return pBitmap -} - -;##################################################################################### - -Gdip_CreateARGBHBITMAPFromBitmap(&pBitmap) { - ; This version is about 25% faster than Gdip_CreateHBITMAPFromBitmap(). - ; Get Bitmap width and height. - DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", &width:=0) - DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", &height:=0) - - ; Convert the source pBitmap into a hBitmap manually. - ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader - hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr") - bi := Buffer(40, 0) ; sizeof(bi) = 40 - NumPut( - "UInt", 40, ; Size - "UInt", width, ; Width - "Int", -height, ; Height - Negative so (0, 0) is top-left. - "ushort", 1, ; Planes - "ushort", 32, ; BitCount / BitsPerPixel - bi) - hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", bi.Ptr, "UInt", 0, "ptr*", &pBits:=0, "ptr", 0, "UInt", 0, "ptr") - obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr") - - ; Transfer data from source pBitmap to an hBitmap manually. - Rect := Buffer(16, 0) ; sizeof(Rect) = 16 - NumPut( - "UInt", width, ; Width - "UInt", height, ; Height - Rect, 8) - BitmapData := Buffer(16+2*A_PtrSize, 0) ; sizeof(BitmapData) = 24, 32 - NumPut( - "UInt", width, ; Width - "UInt", height, ; Height - "Int", 4 * width, ; Stride - "Int", 0xE200B, ; PixelFormat - "ptr", pBits, ; Scan0 - BitmapData) - DllCall("gdiplus\GdipBitmapLockBits" - , "ptr", pBitmap - , "ptr", Rect.Ptr - , "UInt", 5 ; ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly - , "Int", 0xE200B ; Format32bppPArgb - , "ptr", BitmapData.Ptr) ; Contains the pointer (pBits) to the hbm. - DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", BitmapData.Ptr) - - ; Cleanup the hBitmap and device contexts. - DllCall("SelectObject", "ptr", hdc, "ptr", obm) - DllCall("DeleteDC", "ptr", hdc) - - return hbm -} - -;##################################################################################### - -Gdip_CreateBitmapFromHICON(hIcon) -{ - DllCall("gdiplus\GdipCreateBitmapFromHICON", "UPtr", hIcon, "UPtr*", &pBitmap:=0) - return pBitmap -} - -;##################################################################################### - -Gdip_CreateHICONFromBitmap(pBitmap) -{ - DllCall("gdiplus\GdipCreateHICONFromBitmap", "UPtr", pBitmap, "UPtr*", &hIcon:=0) - return hIcon -} - -;##################################################################################### - -Gdip_CreateBitmap(Width, Height, Format:=0x26200A) -{ - DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", Width, "Int", Height, "Int", 0, "Int", Format, "UPtr", 0, "UPtr*", &pBitmap:=0) - return pBitmap -} - -;##################################################################################### - -Gdip_CreateBitmapFromClipboard() -{ - if !DllCall("IsClipboardFormatAvailable", "UInt", 8) { - return -2 - } - - if !DllCall("OpenClipboard", "UPtr", 0) { - return -1 - } - - hBitmap := DllCall("GetClipboardData", "UInt", 2, "UPtr") - - if !DllCall("CloseClipboard") { - return -5 - } - - if !hBitmap { - return -3 - } - - pBitmap := Gdip_CreateBitmapFromHBITMAP(hBitmap) - if (!pBitmap) { - return -4 - } - - DeleteObject(hBitmap) - - return pBitmap -} - -;##################################################################################### - -Gdip_SetBitmapToClipboard(pBitmap) -{ - off1 := A_PtrSize = 8 ? 52 : 44, off2 := A_PtrSize = 8 ? 32 : 24 - hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap) - oi := Buffer(A_PtrSize = 8 ? 104 : 84, 0) - DllCall("GetObject", "UPtr", hBitmap, "Int", oi.Size, "UPtr", oi.Ptr) - hdib := DllCall("GlobalAlloc", "UInt", 2, "UPtr", 40+NumGet(oi, off1, "UInt"), "UPtr") - pdib := DllCall("GlobalLock", "UPtr", hdib, "UPtr") - DllCall("RtlMoveMemory", "UPtr", pdib, "UPtr", oi.Ptr+off2, "UPtr", 40) - DllCall("RtlMoveMemory", "UPtr", pdib+40, "UPtr", NumGet(oi, off2 - (A_PtrSize ? A_PtrSize : 4), "UPtr"), "UPtr", NumGet(oi, off1, "UInt")) - DllCall("GlobalUnlock", "UPtr", hdib) - DllCall("DeleteObject", "UPtr", hBitmap) - DllCall("OpenClipboard", "UPtr", 0) - DllCall("EmptyClipboard") - DllCall("SetClipboardData", "UInt", 8, "UPtr", hdib) - DllCall("CloseClipboard") -} - -;##################################################################################### - -Gdip_CloneBitmapArea(pBitmap, x, y, w, h, Format:=0x26200A) -{ - DllCall("gdiplus\GdipCloneBitmapArea" - , "Float", x - , "Float", y - , "Float", w - , "Float", h - , "Int", Format - , "UPtr", pBitmap - , "UPtr*", &pBitmapDest:=0) - return pBitmapDest -} - -;##################################################################################### -; Create resources -;##################################################################################### - -Gdip_CreatePen(ARGB, w) -{ - DllCall("gdiplus\GdipCreatePen1", "UInt", ARGB, "Float", w, "Int", 2, "UPtr*", &pPen:=0) - return pPen -} - -;##################################################################################### - -Gdip_CreatePenFromBrush(pBrush, w) -{ - DllCall("gdiplus\GdipCreatePen2", "UPtr", pBrush, "Float", w, "Int", 2, "UPtr*", &pPen:=0) - return pPen -} - -;##################################################################################### - -Gdip_BrushCreateSolid(ARGB:=0xff000000) -{ - DllCall("gdiplus\GdipCreateSolidFill", "UInt", ARGB, "UPtr*", &pBrush:=0) - return pBrush -} - -;##################################################################################### - -; HatchStyleHorizontal = 0 -; HatchStyleVertical = 1 -; HatchStyleForwardDiagonal = 2 -; HatchStyleBackwardDiagonal = 3 -; HatchStyleCross = 4 -; HatchStyleDiagonalCross = 5 -; HatchStyle05Percent = 6 -; HatchStyle10Percent = 7 -; HatchStyle20Percent = 8 -; HatchStyle25Percent = 9 -; HatchStyle30Percent = 10 -; HatchStyle40Percent = 11 -; HatchStyle50Percent = 12 -; HatchStyle60Percent = 13 -; HatchStyle70Percent = 14 -; HatchStyle75Percent = 15 -; HatchStyle80Percent = 16 -; HatchStyle90Percent = 17 -; HatchStyleLightDownwardDiagonal = 18 -; HatchStyleLightUpwardDiagonal = 19 -; HatchStyleDarkDownwardDiagonal = 20 -; HatchStyleDarkUpwardDiagonal = 21 -; HatchStyleWideDownwardDiagonal = 22 -; HatchStyleWideUpwardDiagonal = 23 -; HatchStyleLightVertical = 24 -; HatchStyleLightHorizontal = 25 -; HatchStyleNarrowVertical = 26 -; HatchStyleNarrowHorizontal = 27 -; HatchStyleDarkVertical = 28 -; HatchStyleDarkHorizontal = 29 -; HatchStyleDashedDownwardDiagonal = 30 -; HatchStyleDashedUpwardDiagonal = 31 -; HatchStyleDashedHorizontal = 32 -; HatchStyleDashedVertical = 33 -; HatchStyleSmallConfetti = 34 -; HatchStyleLargeConfetti = 35 -; HatchStyleZigZag = 36 -; HatchStyleWave = 37 -; HatchStyleDiagonalBrick = 38 -; HatchStyleHorizontalBrick = 39 -; HatchStyleWeave = 40 -; HatchStylePlaid = 41 -; HatchStyleDivot = 42 -; HatchStyleDottedGrid = 43 -; HatchStyleDottedDiamond = 44 -; HatchStyleShingle = 45 -; HatchStyleTrellis = 46 -; HatchStyleSphere = 47 -; HatchStyleSmallGrid = 48 -; HatchStyleSmallCheckerBoard = 49 -; HatchStyleLargeCheckerBoard = 50 -; HatchStyleOutlinedDiamond = 51 -; HatchStyleSolidDiamond = 52 -; HatchStyleTotal = 53 -Gdip_BrushCreateHatch(ARGBfront, ARGBback, HatchStyle:=0) -{ - DllCall("gdiplus\GdipCreateHatchBrush", "Int", HatchStyle, "UInt", ARGBfront, "UInt", ARGBback, "UPtr*", &pBrush:=0) - return pBrush -} - -;##################################################################################### - -Gdip_CreateTextureBrush(pBitmap, WrapMode:=1, x:=0, y:=0, w:="", h:="") -{ - if !(w && h) { - DllCall("gdiplus\GdipCreateTexture", "UPtr", pBitmap, "Int", WrapMode, "UPtr*", &pBrush:=0) - } else { - DllCall("gdiplus\GdipCreateTexture2", "UPtr", pBitmap, "Int", WrapMode, "Float", x, "Float", y, "Float", w, "Float", h, "UPtr*", &pBrush:=0) - } - - return pBrush -} - -;##################################################################################### - -; WrapModeTile = 0 -; WrapModeTileFlipX = 1 -; WrapModeTileFlipY = 2 -; WrapModeTileFlipXY = 3 -; WrapModeClamp = 4 -Gdip_CreateLineBrush(x1, y1, x2, y2, ARGB1, ARGB2, WrapMode:=1) -{ - CreatePointF(&PointF1:="", x1, y1), CreatePointF(&PointF2:="", x2, y2) - DllCall("gdiplus\GdipCreateLineBrush", "UPtr", PointF1.Ptr, "UPtr", PointF2.Ptr, "UInt", ARGB1, "UInt", ARGB2, "Int", WrapMode, "UPtr*", &LGpBrush:=0) - return LGpBrush -} - -;##################################################################################### - -; LinearGradientModeHorizontal = 0 -; LinearGradientModeVertical = 1 -; LinearGradientModeForwardDiagonal = 2 -; LinearGradientModeBackwardDiagonal = 3 -Gdip_CreateLineBrushFromRect(x, y, w, h, ARGB1, ARGB2, LinearGradientMode:=1, WrapMode:=1) -{ - CreateRectF(&RectF:="", x, y, w, h) - DllCall("gdiplus\GdipCreateLineBrushFromRect", "UPtr", RectF.Ptr, "Int", ARGB1, "Int", ARGB2, "Int", LinearGradientMode, "Int", WrapMode, "UPtr*", &LGpBrush:=0) - return LGpBrush -} - -;##################################################################################### - -Gdip_CloneBrush(pBrush) -{ - DllCall("gdiplus\GdipCloneBrush", "UPtr", pBrush, "UPtr*", &pBrushClone:=0) - return pBrushClone -} - -;##################################################################################### -; Delete resources -;##################################################################################### - -Gdip_DeletePen(pPen) -{ - return DllCall("gdiplus\GdipDeletePen", "UPtr", pPen) -} - -;##################################################################################### - -Gdip_DeleteBrush(pBrush) -{ - return DllCall("gdiplus\GdipDeleteBrush", "UPtr", pBrush) -} - -;##################################################################################### - -Gdip_DisposeImage(pBitmap) -{ - return DllCall("gdiplus\GdipDisposeImage", "UPtr", pBitmap) -} - -;##################################################################################### - -Gdip_DeleteGraphics(pGraphics) -{ - return DllCall("gdiplus\GdipDeleteGraphics", "UPtr", pGraphics) -} - -;##################################################################################### - -Gdip_DisposeImageAttributes(ImageAttr) -{ - return DllCall("gdiplus\GdipDisposeImageAttributes", "UPtr", ImageAttr) -} - -;##################################################################################### - -Gdip_DeleteFont(hFont) -{ - return DllCall("gdiplus\GdipDeleteFont", "UPtr", hFont) -} - -;##################################################################################### - -Gdip_DeleteStringFormat(hFormat) -{ - return DllCall("gdiplus\GdipDeleteStringFormat", "UPtr", hFormat) -} - -;##################################################################################### - -Gdip_DeleteFontFamily(hFamily) -{ - return DllCall("gdiplus\GdipDeleteFontFamily", "UPtr", hFamily) -} - -;##################################################################################### - -Gdip_DeleteMatrix(Matrix) -{ - return DllCall("gdiplus\GdipDeleteMatrix", "UPtr", Matrix) -} - -;##################################################################################### -; Text functions -;##################################################################################### - -Gdip_TextToGraphics(pGraphics, Text, Options, Font:="Arial", Width:="", Height:="", Measure:=0) -{ - IWidth := Width - IHeight := Height - PassBrush := 0 - - - pattern_opts := "i)" - RegExMatch(Options, pattern_opts "X([\-\d\.]+)(p*)", &xpos:="") - RegExMatch(Options, pattern_opts "Y([\-\d\.]+)(p*)", &ypos:="") - RegExMatch(Options, pattern_opts "W([\-\d\.]+)(p*)", &Width:="") - RegExMatch(Options, pattern_opts "H([\-\d\.]+)(p*)", &Height:="") - RegExMatch(Options, pattern_opts "C(?!(entre|enter))([a-f\d]+)", &Colour:="") - RegExMatch(Options, pattern_opts "Top|Up|Bottom|Down|vCentre|vCenter", &vPos:="") - RegExMatch(Options, pattern_opts "NoWrap", &NoWrap:="") - RegExMatch(Options, pattern_opts "R(\d)", &Rendering:="") - RegExMatch(Options, pattern_opts "S(\d+)(p*)", &Size:="") - - if Colour && IsInteger(Colour[2]) && !Gdip_DeleteBrush(Gdip_CloneBrush(Colour[2])) { - PassBrush := 1, pBrush := Colour[2] - } - - if !(IWidth && IHeight) && ((xpos && xpos[2]) || (ypos && ypos[2]) || (Width && Width[2]) || (Height && Height[2]) || (Size && Size[2])) { - return -1 - } - - Style := 0 - Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout" - for eachStyle, valStyle in StrSplit( Styles, "|" ) { - if RegExMatch(Options, "\b" valStyle) - Style |= (valStyle != "StrikeOut") ? (A_Index-1) : 8 - } - - Align := 0 - Alignments := "Near|Left|Centre|Center|Far|Right" - for eachAlignment, valAlignment in StrSplit( Alignments, "|" ) { - if RegExMatch(Options, "\b" valAlignment) { - Align |= A_Index*10//21 ; 0|0|1|1|2|2 - } - } - - xpos := (xpos && (xpos[1] != "")) ? xpos[2] ? IWidth*(xpos[1]/100) : xpos[1] : 0 - ypos := (ypos && (ypos[1] != "")) ? ypos[2] ? IHeight*(ypos[1]/100) : ypos[1] : 0 - Width := (Width && Width[1]) ? Width[2] ? IWidth*(Width[1]/100) : Width[1] : IWidth - Height := (Height && Height[1]) ? Height[2] ? IHeight*(Height[1]/100) : Height[1] : IHeight - - if !PassBrush { - Colour := "0x" (Colour && Colour[2] ? Colour[2] : "ff000000") - } - - Rendering := (Rendering && (Rendering[1] >= 0) && (Rendering[1] <= 5)) ? Rendering[1] : 4 - Size := (Size && (Size[1] > 0)) ? Size[2] ? IHeight*(Size[1]/100) : Size[1] : 12 - - hFamily := Gdip_FontFamilyCreate(Font) - hFont := Gdip_FontCreate(hFamily, Size, Style) - FormatStyle := NoWrap ? 0x4000 | 0x1000 : 0x4000 - hFormat := Gdip_StringFormatCreate(FormatStyle) - pBrush := PassBrush ? pBrush : Gdip_BrushCreateSolid(Colour) - - if !(hFamily && hFont && hFormat && pBrush && pGraphics) { - return !pGraphics ? -2 : !hFamily ? -3 : !hFont ? -4 : !hFormat ? -5 : !pBrush ? -6 : 0 - } - - CreateRectF(&RC:="", xpos, ypos, Width, Height) - Gdip_SetStringFormatAlign(hFormat, Align) - Gdip_SetTextRenderingHint(pGraphics, Rendering) - ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, &RC) - - if vPos { - ReturnRC := StrSplit(ReturnRC, "|") - - if (vPos[0] = "vCentre") || (vPos[0] = "vCenter") - ypos += (Height-ReturnRC[4])//2 - else if (vPos[0] = "Top") || (vPos[0] = "Up") - ypos := 0 - else if (vPos[0] = "Bottom") || (vPos[0] = "Down") - ypos := Height-ReturnRC[4] - - CreateRectF(&RC, xpos, ypos, Width, ReturnRC[4]) - ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, &RC) - } - - if !Measure { - ReturnRC := Gdip_DrawString(pGraphics, Text, hFont, hFormat, pBrush, &RC) - } - - if !PassBrush { - Gdip_DeleteBrush(pBrush) - } - - Gdip_DeleteStringFormat(hFormat) - Gdip_DeleteFont(hFont) - Gdip_DeleteFontFamily(hFamily) - - return ReturnRC -} - -;##################################################################################### - -Gdip_DrawString(pGraphics, sString, hFont, hFormat, pBrush, &RectF) -{ - return DllCall("gdiplus\GdipDrawString" - , "UPtr", pGraphics - , "UPtr", StrPtr(sString) - , "Int", -1 - , "UPtr", hFont - , "UPtr", RectF.Ptr - , "UPtr", hFormat - , "UPtr", pBrush) -} - -;##################################################################################### - -Gdip_MeasureString(pGraphics, sString, hFont, hFormat, &RectF) -{ - RC := Buffer(16) - DllCall("gdiplus\GdipMeasureString" - , "UPtr", pGraphics - , "UPtr", StrPtr(sString) - , "Int", -1 - , "UPtr", hFont - , "UPtr", RectF.Ptr - , "UPtr", hFormat - , "UPtr", RC.Ptr - , "uint*", &Chars:=0 - , "uint*", &Lines:=0) - - return RC.Ptr ? NumGet(RC, 0, "Float") "|" NumGet(RC, 4, "Float") "|" NumGet(RC, 8, "Float") "|" NumGet(RC, 12, "Float") "|" Chars "|" Lines : 0 -} - -; Near = 0 -; Center = 1 -; Far = 2 -Gdip_SetStringFormatAlign(hFormat, Align) -{ - return DllCall("gdiplus\GdipSetStringFormatAlign", "UPtr", hFormat, "Int", Align) -} - -; StringFormatFlagsDirectionRightToLeft = 0x00000001 -; StringFormatFlagsDirectionVertical = 0x00000002 -; StringFormatFlagsNoFitBlackBox = 0x00000004 -; StringFormatFlagsDisplayFormatControl = 0x00000020 -; StringFormatFlagsNoFontFallback = 0x00000400 -; StringFormatFlagsMeasureTrailingSpaces = 0x00000800 -; StringFormatFlagsNoWrap = 0x00001000 -; StringFormatFlagsLineLimit = 0x00002000 -; StringFormatFlagsNoClip = 0x00004000 -Gdip_StringFormatCreate(Format:=0, Lang:=0) -{ - DllCall("gdiplus\GdipCreateStringFormat", "Int", Format, "Int", Lang, "UPtr*", &hFormat:=0) - return hFormat -} - -; Regular = 0 -; Bold = 1 -; Italic = 2 -; BoldItalic = 3 -; Underline = 4 -; Strikeout = 8 -Gdip_FontCreate(hFamily, Size, Style:=0) -{ - DllCall("gdiplus\GdipCreateFont", "UPtr", hFamily, "Float", Size, "Int", Style, "Int", 0, "UPtr*", &hFont:=0) - return hFont -} - -Gdip_FontFamilyCreate(Font) -{ - DllCall("gdiplus\GdipCreateFontFamilyFromName" - , "UPtr", StrPtr(Font) - , "UInt", 0 - , "UPtr*", &hFamily:=0) - - return hFamily -} - -;##################################################################################### -; Matrix functions -;##################################################################################### - -Gdip_CreateAffineMatrix(m11, m12, m21, m22, x, y) -{ - DllCall("gdiplus\GdipCreateMatrix2", "Float", m11, "Float", m12, "Float", m21, "Float", m22, "Float", x, "Float", y, "UPtr*", &Matrix:=0) - return Matrix -} - -Gdip_CreateMatrix() -{ - DllCall("gdiplus\GdipCreateMatrix", "UPtr*", &Matrix:=0) - return Matrix -} - -;##################################################################################### -; GraphicsPath functions -;##################################################################################### - -; Alternate = 0 -; Winding = 1 -Gdip_CreatePath(BrushMode:=0) -{ - DllCall("gdiplus\GdipCreatePath", "Int", BrushMode, "UPtr*", &pPath:=0) - return pPath -} - -Gdip_AddPathEllipse(pPath, x, y, w, h) -{ - return DllCall("gdiplus\GdipAddPathEllipse", "UPtr", pPath, "Float", x, "Float", y, "Float", w, "Float", h) -} - -Gdip_AddPathPolygon(pPath, Points) -{ - Points := StrSplit(Points, "|") - PointsLength := Points.Length - PointF := Buffer(8*PointsLength) - for eachPoint, Point in Points - { - Coord := StrSplit(Point, ",") - NumPut("Float", Coord[1], PointF, 8*(A_Index-1)) - NumPut("Float", Coord[2], PointF, (8*(A_Index-1))+4) - } - - return DllCall("gdiplus\GdipAddPathPolygon", "UPtr", pPath, "UPtr", PointF.Ptr, "Int", PointsLength) -} - -Gdip_DeletePath(pPath) -{ - return DllCall("gdiplus\GdipDeletePath", "UPtr", pPath) -} - -;##################################################################################### -; Quality functions -;##################################################################################### - -; SystemDefault = 0 -; SingleBitPerPixelGridFit = 1 -; SingleBitPerPixel = 2 -; AntiAliasGridFit = 3 -; AntiAlias = 4 -Gdip_SetTextRenderingHint(pGraphics, RenderingHint) -{ - return DllCall("gdiplus\GdipSetTextRenderingHint", "UPtr", pGraphics, "Int", RenderingHint) -} - -; Default = 0 -; LowQuality = 1 -; HighQuality = 2 -; Bilinear = 3 -; Bicubic = 4 -; NearestNeighbor = 5 -; HighQualityBilinear = 6 -; HighQualityBicubic = 7 -Gdip_SetInterpolationMode(pGraphics, InterpolationMode) -{ - return DllCall("gdiplus\GdipSetInterpolationMode", "UPtr", pGraphics, "Int", InterpolationMode) -} - -; Default = 0 -; HighSpeed = 1 -; HighQuality = 2 -; None = 3 -; AntiAlias = 4 -Gdip_SetSmoothingMode(pGraphics, SmoothingMode) -{ - return DllCall("gdiplus\GdipSetSmoothingMode", "UPtr", pGraphics, "Int", SmoothingMode) -} - -; CompositingModeSourceOver = 0 (blended) -; CompositingModeSourceCopy = 1 (overwrite) -Gdip_SetCompositingMode(pGraphics, CompositingMode:=0) -{ - return DllCall("gdiplus\GdipSetCompositingMode", "UPtr", pGraphics, "Int", CompositingMode) -} - -;##################################################################################### -; Extra functions -;##################################################################################### - -Gdip_Startup() -{ - if (!DllCall("LoadLibrary", "str", "gdiplus", "UPtr")) { - throw Error("Could not load GDI+ library") - } - - si := Buffer(A_PtrSize = 4 ? 20:32, 0) ; sizeof(GdiplusStartupInputEx) = 20, 32 - NumPut("uint", 0x2, si) - NumPut("uint", 0x4, si, A_PtrSize = 4 ? 16:24) - DllCall("gdiplus\GdiplusStartup", "UPtr*", &pToken:=0, "Ptr", si, "UPtr", 0) - if (!pToken) { - throw Error("Gdiplus failed to start. Please ensure you have gdiplus on your system") - } - - return pToken -} - -Gdip_Shutdown(pToken) -{ - DllCall("gdiplus\GdiplusShutdown", "UPtr", pToken) - hModule := DllCall("GetModuleHandle", "str", "gdiplus", "UPtr") - if (!hModule) { - throw Error("GDI+ library was unloaded before shutdown") - } - if (!DllCall("FreeLibrary", "UPtr", hModule)) { - throw Error("Could not free GDI+ library") - } - - return 0 -} - -; Prepend = 0; The new operation is applied before the old operation. -; Append = 1; The new operation is applied after the old operation. -Gdip_RotateWorldTransform(pGraphics, Angle, MatrixOrder:=0) -{ - return DllCall("gdiplus\GdipRotateWorldTransform", "UPtr", pGraphics, "Float", Angle, "Int", MatrixOrder) -} - -Gdip_ScaleWorldTransform(pGraphics, x, y, MatrixOrder:=0) -{ - return DllCall("gdiplus\GdipScaleWorldTransform", "UPtr", pGraphics, "Float", x, "Float", y, "Int", MatrixOrder) -} - -Gdip_TranslateWorldTransform(pGraphics, x, y, MatrixOrder:=0) -{ - return DllCall("gdiplus\GdipTranslateWorldTransform", "UPtr", pGraphics, "Float", x, "Float", y, "Int", MatrixOrder) -} - -Gdip_ResetWorldTransform(pGraphics) -{ - return DllCall("gdiplus\GdipResetWorldTransform", "UPtr", pGraphics) -} - -Gdip_GetRotatedTranslation(Width, Height, Angle, &xTranslation, &yTranslation) -{ - pi := 3.14159, TAngle := Angle*(pi/180) - - Bound := (Angle >= 0) ? Mod(Angle, 360) : 360-Mod(-Angle, -360) - if ((Bound >= 0) && (Bound <= 90)) { - xTranslation := Height*Sin(TAngle), yTranslation := 0 - } else if ((Bound > 90) && (Bound <= 180)) { - xTranslation := (Height*Sin(TAngle))-(Width*Cos(TAngle)), yTranslation := -Height*Cos(TAngle) - } else if ((Bound > 180) && (Bound <= 270)) { - xTranslation := -(Width*Cos(TAngle)), yTranslation := -(Height*Cos(TAngle))-(Width*Sin(TAngle)) - } else if ((Bound > 270) && (Bound <= 360)) { - xTranslation := 0, yTranslation := -Width*Sin(TAngle) - } -} - -Gdip_GetRotatedDimensions(Width, Height, Angle, &RWidth, &RHeight) -{ - pi := 3.14159, TAngle := Angle*(pi/180) - - if !(Width && Height) { - return -1 - } - - RWidth := Ceil(Abs(Width*Cos(TAngle))+Abs(Height*Sin(TAngle))) - RHeight := Ceil(Abs(Width*Sin(TAngle))+Abs(Height*Cos(Tangle))) -} - -; RotateNoneFlipNone = 0 -; Rotate90FlipNone = 1 -; Rotate180FlipNone = 2 -; Rotate270FlipNone = 3 -; RotateNoneFlipX = 4 -; Rotate90FlipX = 5 -; Rotate180FlipX = 6 -; Rotate270FlipX = 7 -; RotateNoneFlipY = Rotate180FlipX -; Rotate90FlipY = Rotate270FlipX -; Rotate180FlipY = RotateNoneFlipX -; Rotate270FlipY = Rotate90FlipX -; RotateNoneFlipXY = Rotate180FlipNone -; Rotate90FlipXY = Rotate270FlipNone -; Rotate180FlipXY = RotateNoneFlipNone -; Rotate270FlipXY = Rotate90FlipNone - -Gdip_ImageRotateFlip(pBitmap, RotateFlipType:=1) -{ - return DllCall("gdiplus\GdipImageRotateFlip", "UPtr", pBitmap, "Int", RotateFlipType) -} - -; Replace = 0 -; Intersect = 1 -; Union = 2 -; Xor = 3 -; Exclude = 4 -; Complement = 5 -Gdip_SetClipRect(pGraphics, x, y, w, h, CombineMode:=0) -{ - return DllCall("gdiplus\GdipSetClipRect", "UPtr", pGraphics, "Float", x, "Float", y, "Float", w, "Float", h, "Int", CombineMode) -} - -Gdip_SetClipPath(pGraphics, pPath, CombineMode:=0) -{ - return DllCall("gdiplus\GdipSetClipPath", "UPtr", pGraphics, "UPtr", pPath, "Int", CombineMode) -} - -Gdip_ResetClip(pGraphics) -{ - return DllCall("gdiplus\GdipResetClip", "UPtr", pGraphics) -} - -Gdip_GetClipRegion(pGraphics) -{ - Region := Gdip_CreateRegion() - DllCall("gdiplus\GdipGetClip", "UPtr", pGraphics, "UPtr", Region) - return Region -} - -Gdip_SetClipRegion(pGraphics, Region, CombineMode:=0) -{ - return DllCall("gdiplus\GdipSetClipRegion", "UPtr", pGraphics, "UPtr", Region, "Int", CombineMode) -} - -Gdip_CreateRegion() -{ - DllCall("gdiplus\GdipCreateRegion", "UPtr*", &Region:=0) - return Region -} - -Gdip_DeleteRegion(Region) -{ - return DllCall("gdiplus\GdipDeleteRegion", "UPtr", Region) -} - -;##################################################################################### -; BitmapLockBits -;##################################################################################### - -Gdip_LockBits(pBitmap, x, y, w, h, &Stride, &Scan0, &BitmapData, LockMode := 3, PixelFormat := 0x26200a) -{ - CreateRect(&_Rect:="", x, y, w, h) - BitmapData := Buffer(16+2*(A_PtrSize ? A_PtrSize : 4), 0) - _E := DllCall("Gdiplus\GdipBitmapLockBits", "UPtr", pBitmap, "UPtr", _Rect.Ptr, "UInt", LockMode, "Int", PixelFormat, "UPtr", BitmapData.Ptr) - Stride := NumGet(BitmapData, 8, "Int") - Scan0 := NumGet(BitmapData, 16, "UPtr") - return _E -} - -;##################################################################################### - -Gdip_UnlockBits(pBitmap, &BitmapData) -{ - return DllCall("Gdiplus\GdipBitmapUnlockBits", "UPtr", pBitmap, "UPtr", BitmapData.Ptr) -} - -;##################################################################################### - -Gdip_SetLockBitPixel(ARGB, Scan0, x, y, Stride) -{ - Numput("UInt", ARGB, Scan0+0, (x*4)+(y*Stride)) -} - -;##################################################################################### - -Gdip_GetLockBitPixel(Scan0, x, y, Stride) -{ - return NumGet(Scan0+0, (x*4)+(y*Stride), "UInt") -} - -;##################################################################################### - -Gdip_PixelateBitmap(pBitmap, &pBitmapOut, BlockSize) -{ - static PixelateBitmap := "" - - if (!PixelateBitmap) - { - if A_PtrSize != 8 ; x86 machine code - MCode_PixelateBitmap := " - (LTrim Join - 558BEC83EC3C8B4514538B5D1C99F7FB56578BC88955EC894DD885C90F8E830200008B451099F7FB8365DC008365E000894DC88955F08945E833FF897DD4 - 397DE80F8E160100008BCB0FAFCB894DCC33C08945F88945FC89451C8945143BD87E608B45088D50028BC82BCA8BF02BF2418945F48B45E02955F4894DC4 - 8D0CB80FAFCB03CA895DD08BD1895DE40FB64416030145140FB60201451C8B45C40FB604100145FC8B45F40FB604020145F883C204FF4DE475D6034D18FF - 4DD075C98B4DCC8B451499F7F98945148B451C99F7F989451C8B45FC99F7F98945FC8B45F899F7F98945F885DB7E648B450C8D50028BC82BCA83C103894D - C48BC82BCA41894DF48B4DD48945E48B45E02955E48D0C880FAFCB03CA895DD08BD18BF38A45148B7DC48804178A451C8B7DF488028A45FC8804178A45F8 - 8B7DE488043A83C2044E75DA034D18FF4DD075CE8B4DCC8B7DD447897DD43B7DE80F8CF2FEFFFF837DF0000F842C01000033C08945F88945FC89451C8945 - 148945E43BD87E65837DF0007E578B4DDC034DE48B75E80FAF4D180FAFF38B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945CC0F - B6440E030145140FB60101451C0FB6440F010145FC8B45F40FB604010145F883C104FF4DCC75D8FF45E4395DE47C9B8B4DF00FAFCB85C9740B8B451499F7 - F9894514EB048365140033F63BCE740B8B451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB - 038975F88975E43BDE7E5A837DF0007E4C8B4DDC034DE48B75E80FAF4D180FAFF38B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955CC8A55 - 1488540E038A551C88118A55FC88540F018A55F888140183C104FF4DCC75DFFF45E4395DE47CA68B45180145E0015DDCFF4DC80F8594FDFFFF8B451099F7 - FB8955F08945E885C00F8E450100008B45EC0FAFC38365DC008945D48B45E88945CC33C08945F88945FC89451C8945148945103945EC7E6085DB7E518B4D - D88B45080FAFCB034D108D50020FAF4D18034DDC8BF08BF88945F403CA2BF22BFA2955F4895DC80FB6440E030145140FB60101451C0FB6440F010145FC8B - 45F40FB604080145F883C104FF4DC875D8FF45108B45103B45EC7CA08B4DD485C9740B8B451499F7F9894514EB048365140033F63BCE740B8B451C99F7F9 - 89451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975103975EC7E5585DB7E468B4DD88B450C - 0FAFCB034D108D50020FAF4D18034DDC8BF08BF803CA2BF22BFA2BC2895DC88A551488540E038A551C88118A55FC88540F018A55F888140183C104FF4DC8 - 75DFFF45108B45103B45EC7CAB8BC3C1E0020145DCFF4DCC0F85CEFEFFFF8B4DEC33C08945F88945FC89451C8945148945103BC87E6C3945F07E5C8B4DD8 - 8B75E80FAFCB034D100FAFF30FAF4D188B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945C80FB6440E030145140FB60101451C0F - B6440F010145FC8B45F40FB604010145F883C104FF4DC875D833C0FF45108B4DEC394D107C940FAF4DF03BC874068B451499F7F933F68945143BCE740B8B - 451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975083975EC7E63EB0233F639 - 75F07E4F8B4DD88B75E80FAFCB034D080FAFF30FAF4D188B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955108A551488540E038A551C8811 - 8A55FC88540F018A55F888140883C104FF4D1075DFFF45088B45083B45EC7C9F5F5E33C05BC9C21800 - )" - else ; x64 machine code - MCode_PixelateBitmap := " - (LTrim Join - 4489442418488954241048894C24085355565741544155415641574883EC28418BC1448B8C24980000004C8BDA99488BD941F7F9448BD0448BFA8954240C - 448994248800000085C00F8E9D020000418BC04533E4458BF299448924244C8954241041F7F933C9898C24980000008BEA89542404448BE889442408EB05 - 4C8B5C24784585ED0F8E1A010000458BF1418BFD48897C2418450FAFF14533D233F633ED4533E44533ED4585C97E5B4C63BC2490000000418D040A410FAF - C148984C8D441802498BD9498BD04D8BD90FB642010FB64AFF4403E80FB60203E90FB64AFE4883C2044403E003F149FFCB75DE4D03C748FFCB75D0488B7C - 24188B8C24980000004C8B5C2478418BC59941F7FE448BE8418BC49941F7FE448BE08BC59941F7FE8BE88BC69941F7FE8BF04585C97E4048639C24900000 - 004103CA4D8BC1410FAFC94863C94A8D541902488BCA498BC144886901448821408869FF408871FE4883C10448FFC875E84803D349FFC875DA8B8C249800 - 0000488B5C24704C8B5C24784183C20448FFCF48897C24180F850AFFFFFF8B6C2404448B2424448B6C24084C8B74241085ED0F840A01000033FF33DB4533 - DB4533D24533C04585C97E53488B74247085ED7E42438D0C04418BC50FAF8C2490000000410FAFC18D04814863C8488D5431028BCD0FB642014403D00FB6 - 024883C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC17CB28BCD410FAFC985C9740A418BC299F7F98BF0EB0233F685C9740B418BC3 - 99F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585C97E4D4C8B74247885ED7E3841 - 8D0C14418BC50FAF8C2490000000410FAFC18D04814863C84A8D4431028BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2413BD17CBD - 4C8B7424108B8C2498000000038C2490000000488B5C24704503E149FFCE44892424898C24980000004C897424100F859EFDFFFF448B7C240C448B842480 - 000000418BC09941F7F98BE8448BEA89942498000000896C240C85C00F8E3B010000448BAC2488000000418BCF448BF5410FAFC9898C248000000033FF33 - ED33F64533DB4533D24533C04585FF7E524585C97E40418BC5410FAFC14103C00FAF84249000000003C74898488D541802498BD90FB642014403D00FB602 - 4883C2044403D80FB642FB03F00FB642FA03E848FFCB75DE488B5C247041FFC0453BC77CAE85C9740B418BC299F7F9448BE0EB034533E485C9740A418BC3 - 99F7F98BD8EB0233DB85C9740A8BC699F7F9448BD8EB034533DB85C9740A8BC599F7F9448BD0EB034533D24533C04585FF7E4E488B4C24784585C97E3541 - 8BC5410FAFC14103C00FAF84249000000003C74898488D540802498BC144886201881A44885AFF448852FE4883C20448FFC875E941FFC0453BC77CBE8B8C - 2480000000488B5C2470418BC1C1E00203F849FFCE0F85ECFEFFFF448BAC24980000008B6C240C448BA4248800000033FF33DB4533DB4533D24533C04585 - FF7E5A488B7424704585ED7E48418BCC8BC5410FAFC94103C80FAF8C2490000000410FAFC18D04814863C8488D543102418BCD0FB642014403D00FB60248 - 83C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC77CAB418BCF410FAFCD85C9740A418BC299F7F98BF0EB0233F685C9740B418BC399 - F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585FF7E4E4585ED7E42418BCC8BC541 - 0FAFC903CA0FAF8C2490000000410FAFC18D04814863C8488B442478488D440102418BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2 - 413BD77CB233C04883C428415F415E415D415C5F5E5D5BC3 - )" - - PixelateBitmap := Buffer(StrLen(MCode_PixelateBitmap)//2) - nCount := StrLen(MCode_PixelateBitmap)//2 - loop nCount { - NumPut("UChar", "0x" SubStr(MCode_PixelateBitmap, (2*A_Index)-1, 2), PixelateBitmap, A_Index-1) - } - DllCall("VirtualProtect", "UPtr", PixelateBitmap.Ptr, "UPtr", PixelateBitmap.Size, "UInt", 0x40, "UPtr*", 0) - } - - Gdip_GetImageDimensions(pBitmap, &Width:="", &Height:="") - - if (Width != Gdip_GetImageWidth(pBitmapOut) || Height != Gdip_GetImageHeight(pBitmapOut)) - return -1 - if (BlockSize > Width || BlockSize > Height) - return -2 - - E1 := Gdip_LockBits(pBitmap, 0, 0, Width, Height, &Stride1:="", &Scan01:="", &BitmapData1:="") - E2 := Gdip_LockBits(pBitmapOut, 0, 0, Width, Height, &Stride2:="", &Scan02:="", &BitmapData2:="") - if (E1 || E2) - return -3 - - ; E := - unused exit code - DllCall(PixelateBitmap.Ptr, "UPtr", Scan01, "UPtr", Scan02, "Int", Width, "Int", Height, "Int", Stride1, "Int", BlockSize) - - Gdip_UnlockBits(pBitmap, &BitmapData1), Gdip_UnlockBits(pBitmapOut, &BitmapData2) - - return 0 -} - -;##################################################################################### - -Gdip_ToARGB(A, R, G, B) -{ - return (A << 24) | (R << 16) | (G << 8) | B -} - -;##################################################################################### - -Gdip_FromARGB(ARGB, &A, &R, &G, &B) -{ - A := (0xff000000 & ARGB) >> 24 - R := (0x00ff0000 & ARGB) >> 16 - G := (0x0000ff00 & ARGB) >> 8 - B := 0x000000ff & ARGB -} - -;##################################################################################### - -Gdip_AFromARGB(ARGB) -{ - return (0xff000000 & ARGB) >> 24 -} - -;##################################################################################### - -Gdip_RFromARGB(ARGB) -{ - return (0x00ff0000 & ARGB) >> 16 -} - -;##################################################################################### - -Gdip_GFromARGB(ARGB) -{ - return (0x0000ff00 & ARGB) >> 8 -} - -;##################################################################################### - -Gdip_BFromARGB(ARGB) -{ - return 0x000000ff & ARGB -} - -;##################################################################################### - -StrGetB(Address, Length:=-1, Encoding:=0) -{ - ; Flexible parameter handling: - if !IsInteger(Length) { - Encoding := Length, Length := -1 - } - - ; Check for obvious errors. - if (Address+0 < 1024) { - return - } - - ; Ensure 'Encoding' contains a numeric identifier. - if (Encoding = "UTF-16") { - Encoding := 1200 - } else if (Encoding = "UTF-8") { - Encoding := 65001 - } else if SubStr(Encoding,1,2)="CP" { - Encoding := SubStr(Encoding,3) - } - - if !Encoding { ; "" or 0 - ; No conversion necessary, but we might not want the whole string. - if (Length == -1) - Length := DllCall("lstrlen", "Ptr", Address) - VarSetStrCapacity(&myString, Length) - DllCall("lstrcpyn", "str", myString, "Ptr", Address, "Int", Length + 1) - - } else if (Encoding = 1200) { ; UTF-16 - char_count := DllCall("WideCharToMultiByte", "UInt", 0, "UInt", 0x400, "Ptr", Address, "Int", Length, "UInt", 0, "UInt", 0, "UInt", 0, "UInt", 0) - VarSetStrCapacity(&myString, char_count) - DllCall("WideCharToMultiByte", "UInt", 0, "UInt", 0x400, "Ptr", Address, "Int", Length, "str", myString, "Int", char_count, "UInt", 0, "UInt", 0) - - } else if IsInteger(Encoding) { - ; Convert from target encoding to UTF-16 then to the active code page. - char_count := DllCall("MultiByteToWideChar", "UInt", Encoding, "UInt", 0, "Ptr", Address, "Int", Length, "Ptr", 0, "Int", 0) - VarSetStrCapacity(&myString, char_count * 2) - char_count := DllCall("MultiByteToWideChar", "UInt", Encoding, "UInt", 0, "Ptr", Address, "Int", Length, "Ptr", myString.Ptr, "Int", char_count * 2) - myString := StrGetB(myString.Ptr, char_count, 1200) - } - - return myString -} - - -; ====================================================================================================================== -; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx -; by 'just me' -; https://autohotkey.com/boards/viewtopic.php?f=6&t=4606 -; ====================================================================================================================== -GetMonitorCount() -{ - Monitors := MDMF_Enum() - for k,v in Monitors { - count := A_Index - } - return count -} - -GetMonitorInfo(MonitorNum) -{ - Monitors := MDMF_Enum() - for k,v in Monitors { - if (v.Num = MonitorNum) { - return v - } - } -} - -GetPrimaryMonitor() -{ - Monitors := MDMF_Enum() - for k,v in Monitors { - if (v.Primary) { - return v.Num - } - } -} -; ---------------------------------------------------------------------------------------------------------------------- -; Name ..........: MDMF - Multiple Display Monitor Functions -; Description ...: Various functions for multiple display monitor environments -; Tested with ...: AHK 1.1.32.00 (A32/U32/U64) and 2.0-a108-a2fa0498 (U32/U64) -; Original Author: just me (https://www.autohotkey.com/boards/viewtopic.php?f=6&t=4606) -; Mod Authors ...: iPhilip, guest3456 -; Changes .......: Modified to work with v2.0-a108 and changed 'Count' key to 'TotalCount' to avoid conflicts -; ................ Modified MDMF_Enum() so that it works under both AHK v1 and v2. -; ................ Modified MDMF_EnumProc() to provide Count and Primary keys to the Monitors array. -; ................ Modified MDMF_FromHWND() to allow flag values that determine the function's return value if the -; ................ window does not intersect any display monitor. -; ................ Modified MDMF_FromPoint() to allow the cursor position to be returned ByRef if not specified and -; ................ allow flag values that determine the function's return value if the point is not contained within -; ................ any display monitor. -; ................ Modified MDMF_FromRect() to allow flag values that determine the function's return value if the -; ................ rectangle does not intersect any display monitor. -;................. Modified MDMF_GetInfo() with minor changes. -; ---------------------------------------------------------------------------------------------------------------------- -; -; ====================================================================================================================== -; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx ======================= -; ====================================================================================================================== -; Enumerates display monitors and returns an object containing the properties of all monitors or the specified monitor. -; ====================================================================================================================== -MDMF_Enum(HMON := "") { - static EnumProc := CallbackCreate(MDMF_EnumProc) - static Monitors := Map() - - if (HMON = "") { ; new enumeration - Monitors := Map("TotalCount", 0) - if !DllCall("User32.dll\EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", EnumProc, "Ptr", ObjPtr(Monitors), "Int") - return False - } - - return (HMON = "") ? Monitors : Monitors.HasKey(HMON) ? Monitors[HMON] : False -} -; ====================================================================================================================== -; Callback function that is called by the MDMF_Enum function. -; ====================================================================================================================== -MDMF_EnumProc(HMON, HDC, PRECT, ObjectAddr) { - Monitors := ObjFromPtrAddRef(ObjectAddr) - - Monitors[HMON] := MDMF_GetInfo(HMON) - Monitors["TotalCount"]++ - if (Monitors[HMON].Primary) { - Monitors["Primary"] := HMON - } - - return true -} -; ====================================================================================================================== -; Retrieves the display monitor that has the largest area of intersection with a specified window. -; The following flag values determine the function's return value if the window does not intersect any display monitor: -; MONITOR_DEFAULTTONULL = 0 - Returns NULL. -; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. -; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window. -; ====================================================================================================================== -MDMF_FromHWND(HWND, Flag := 0) { - return DllCall("User32.dll\MonitorFromWindow", "Ptr", HWND, "UInt", Flag, "Ptr") -} -; ====================================================================================================================== -; Retrieves the display monitor that contains a specified point. -; If either X or Y is empty, the function will use the current cursor position for this value and return it ByRef. -; The following flag values determine the function's return value if the point is not contained within any -; display monitor: -; MONITOR_DEFAULTTONULL = 0 - Returns NULL. -; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. -; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the point. -; ====================================================================================================================== -MDMF_FromPoint(&X:="", &Y:="", Flag:=0) { - if (X = "") || (Y = "") { - PT := Buffer(8, 0) - DllCall("User32.dll\GetCursorPos", "Ptr", PT.Ptr, "Int") - - if (X = "") { - X := NumGet(PT, 0, "Int") - } - - if (Y = "") { - Y := NumGet(PT, 4, "Int") - } - } - return DllCall("User32.dll\MonitorFromPoint", "Int64", (X & 0xFFFFFFFF) | (Y << 32), "UInt", Flag, "Ptr") -} -; ====================================================================================================================== -; Retrieves the display monitor that has the largest area of intersection with a specified rectangle. -; Parameters are consistent with the common AHK definition of a rectangle, which is X, Y, W, H instead of -; Left, Top, Right, Bottom. -; The following flag values determine the function's return value if the rectangle does not intersect any -; display monitor: -; MONITOR_DEFAULTTONULL = 0 - Returns NULL. -; MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. -; MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the rectangle. -; ====================================================================================================================== -MDMF_FromRect(X, Y, W, H, Flag := 0) { - RC := Buffer(16, 0) - NumPut("Int", X, "Int", Y, "Int", X + W, "Int", Y + H, RC) - return DllCall("User32.dll\MonitorFromRect", "Ptr", RC.Ptr, "UInt", Flag, "Ptr") -} -; ====================================================================================================================== -; Retrieves information about a display monitor. -; ====================================================================================================================== -MDMF_GetInfo(HMON) { - MIEX := Buffer(40 + (32 << !!1)) - NumPut("UInt", MIEX.Size, MIEX) - if DllCall("User32.dll\GetMonitorInfo", "Ptr", HMON, "Ptr", MIEX.Ptr, "Int") { - return {Name: (Name := StrGet(MIEX.Ptr + 40, 32)) ; CCHDEVICENAME = 32 - , Num: RegExReplace(Name, ".*(\d+)$", "$1") - , Left: NumGet(MIEX, 4, "Int") ; display rectangle - , Top: NumGet(MIEX, 8, "Int") ; " - , Right: NumGet(MIEX, 12, "Int") ; " - , Bottom: NumGet(MIEX, 16, "Int") ; " - , WALeft: NumGet(MIEX, 20, "Int") ; work area - , WATop: NumGet(MIEX, 24, "Int") ; " - , WARight: NumGet(MIEX, 28, "Int") ; " - , WABottom: NumGet(MIEX, 32, "Int") ; " - , Primary: NumGet(MIEX, 36, "UInt")} ; contains a non-zero value for the primary monitor. - } - return False -} - - -; Based on WinGetClientPos by dd900 and Frosti - https://www.autohotkey.com/boards/viewtopic.php?t=484 -WinGetRect( hwnd, &x:="", &y:="", &w:="", &h:="" ) { - Ptr := A_PtrSize ? "UPtr" : "UInt" - CreateRect(&winRect, 0, 0, 0, 0) ;is 16 on both 32 and 64 - ;VarSetCapacity( winRect, 16, 0 ) ; Alternative of above two lines - DllCall( "GetWindowRect", "Ptr", hwnd, "Ptr", winRect ) - x := NumGet(winRect, 0, "UInt") - y := NumGet(winRect, 4, "UInt") - w := NumGet(winRect, 8, "UInt") - x - h := NumGet(winRect, 12, "UInt") - y -} diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 856e86b..adad354 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 - 2025 Xuesong Peng + * Copyright (c) 2005 Tim * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/README.md b/README.md index 05c8592..8d93ba9 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ rabbit/ - [librime](https://github.com/rime/librime) - [OpenCC](https://github.com/BYVoid/OpenCC) +- [AHKv2-Gdip](https://github.com/buliasz/AHKv2-Gdip) - [librime-ahk](https://github.com/rimeinn/librime-ahk) - [GetCaretPos](https://github.com/Descolada/AHK-v2-libraries) - [GetCaretPosEx](https://github.com/Tebayaki/AutoHotkeyScripts/tree/main/lib/GetCaretPosEx) From 18d9115dbb7d065ed82610f23f0369f894add228 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 30 Aug 2025 10:25:05 +0800 Subject: [PATCH 19/38] feat: add theme dialog (cannot save settings yet) --- Lib/RabbitCandidateBox.ahk | 1 + Lib/RabbitThemesUI.ahk | 5 +++-- RabbitDeployer.ahk | 25 ++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index adad354..902f159 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -36,6 +36,7 @@ class CandidateBox { oBitmap := 0 pGraphics := 0 hFont := 0 + hFamily := 0 hFormat := 0 static isHidden := 1 diff --git a/Lib/RabbitThemesUI.ahk b/Lib/RabbitThemesUI.ahk index 3ecf5c4..3be338c 100644 --- a/Lib/RabbitThemesUI.ahk +++ b/Lib/RabbitThemesUI.ahk @@ -227,7 +227,8 @@ class CandidatePreview { } class ThemesGUI { - __New() { + __New(result) { + this.result := result this.preset_color_schemes := Map() this.colorSchemeMap := Map() this.previewFontName := UIStyle.font_face @@ -316,7 +317,7 @@ class ThemesGUI { rime.config_set_string(config, "style/font_face", this.previewFontName) UIStyle.Update(config, init := true) rime.config_close(config) - box.UpdateUIStyle() + this.result.yes := true } this.gui.Hide() diff --git a/RabbitDeployer.ahk b/RabbitDeployer.ahk index 93d75cd..fe6bf0a 100644 --- a/RabbitDeployer.ahk +++ b/RabbitDeployer.ahk @@ -24,6 +24,7 @@ #Include #Include +#Include ;@Ahk2Exe-SetMainIcon Lib\rabbit-alt.ico global IN_MAINTENANCE := true @@ -110,6 +111,24 @@ ConfigureSwitcher(levers, switcher_settings, &reconfigured) { return false } +ConfigureUI(&reconfigured) { + if !IsSet(reconfigured) + reconfigured := false + result := { + yes : false + } + dialog := ThemesGUI(result) + dialog.Show() + WinWaitClose(dialog.gui) + + if result.yes { + ; settings already saved + reconfigured := true + return true + } + return false +} + class Configurator extends Class { __New() { CreateFileIfNotExist("default.custom.yaml") @@ -130,10 +149,14 @@ class Configurator extends Class { switcher_settings := levers.switcher_settings_init() skip_switcher_settings := installing && !levers.is_first_run(switcher_settings) + skip_ui_style_settings := installing ; TODO: do we need to check first run? if !skip_switcher_settings { - ConfigureSwitcher(levers, switcher_settings, &reconfigured) + if !ConfigureSwitcher(levers, switcher_settings, &reconfigured) + skip_ui_style_settings := true ; user cancelled } + if !skip_ui_style_settings + ConfigureUI(&reconfigured) levers.custom_settings_destroy(switcher_settings) From 48d93f202ed4ea4577a53f28ee991b56088ae495 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 31 Aug 2025 10:59:09 +0800 Subject: [PATCH 20/38] feat: complete theme dialog (missing font sel yet) --- Lib/RabbitCandidateBox.ahk | 4 +- README.md | 1 + RabbitDeployer.ahk | 162 +++++++++++++++++++++++++++++++++++-- 3 files changed, 158 insertions(+), 9 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 902f159..b05741f 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * -*/ + */ #Include #Include @@ -694,4 +694,4 @@ GetCompositionText(composition, &pre_selected, &selected, &post_selected) { pre_selected := StrGet(preedit_buffer, "UTF-8") return false } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 8d93ba9..917e9c2 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ rabbit/ - [GetCaretPos](https://github.com/Descolada/AHK-v2-libraries) - [GetCaretPosEx](https://github.com/Tebayaki/AutoHotkeyScripts/tree/main/lib/GetCaretPosEx) - [东风破](https://github.com/rime/plum) +- [小狼毫](https://github.com/rime/weasel) 以及一些代码片段,在注释中注明了来源链接 diff --git a/RabbitDeployer.ahk b/RabbitDeployer.ahk index fe6bf0a..24f01d7 100644 --- a/RabbitDeployer.ahk +++ b/RabbitDeployer.ahk @@ -111,19 +111,22 @@ ConfigureSwitcher(levers, switcher_settings, &reconfigured) { return false } -ConfigureUI(&reconfigured) { +ConfigureUI(levers, ui_style_settings, &reconfigured) { if !IsSet(reconfigured) reconfigured := false + local settings := ui_style_settings.settings + if !levers.load_settings(settings) + return false result := { yes : false } - dialog := ThemesGUI(result) + dialog := UIStyleSettingsDialog(ui_style_settings, result) dialog.Show() - WinWaitClose(dialog.gui) + WinWaitClose(dialog) if result.yes { - ; settings already saved - reconfigured := true + if levers.save_settings(settings) + reconfigured := true return true } return false @@ -148,15 +151,16 @@ class Configurator extends Class { return 1 switcher_settings := levers.switcher_settings_init() + ui_style_settings := UIStyleSettings() skip_switcher_settings := installing && !levers.is_first_run(switcher_settings) - skip_ui_style_settings := installing ; TODO: do we need to check first run? + skip_ui_style_settings := installing && !levers.is_first_run(ui_style_settings.settings) if !skip_switcher_settings { if !ConfigureSwitcher(levers, switcher_settings, &reconfigured) skip_ui_style_settings := true ; user cancelled } if !skip_ui_style_settings - ConfigureUI(&reconfigured) + ConfigureUI(levers, ui_style_settings, &reconfigured) levers.custom_settings_destroy(switcher_settings) @@ -392,6 +396,7 @@ class SwitcherSettingsDialog extends Gui { this.hotkeys := this.AddEdit("-Multi ReadOnly r1 w505") this.proxy_prompt := this.AddText("XS", "代理服务器:") this.proxy := this.AddEdit("X+10 -Multi r1 w300") + DllCall("SendMessage", "Ptr", this.proxy.Hwnd, "UInt", EM_SETCUEBANNER := 0x1501, "UPtr", true, "WStr", "如 http://127.0.0.1:7890", "Ptr") this.use_git := this.AddCheckbox("X+20", "使用 Git") this.use_git.Value := 1 this.more_schemas := this.AddButton("XS w155", "获取更多输入方案…") @@ -514,3 +519,146 @@ class SwitcherSettingsDialog extends Gui { this.Destroy() } } + +class UIStyleSettings { + __New() { + this.api := RimeLeversApi() + this.settings := this.api.custom_settings_init("rabbit", "Rabbit.UIStyleSettings") + } + + GetPresetColorSchemes() { + global rime + local result := [] + if !config := this.api.settings_get_config(this.settings) + return result + if !rime || !preset := rime.config_begin_map(config, "preset_color_schemes") + return result + while rime.config_next(preset) { + local name_key := preset.path . "/name" + if !name := rime.config_get_cstring(config, name_key) + continue + local author_key := preset.path . "/author" + local author := rime.config_get_cstring(config, author_key) + UIStyle.UpdateColor(config, StrLower(preset.key)) + result.Push({ + color_scheme_id: preset.key, + name: name, + author: author, + border_color: UIStyle.border_color, + text_color: UIStyle.text_color, + back_color: UIStyle.back_color, + hilited_text_color: UIStyle.hilited_text_color, + hilited_back_color: UIStyle.hilited_back_color, + hilited_candidate_text_color: UIStyle.hilited_candidate_text_color, + hilited_candidate_back_color: UIStyle.hilited_candidate_back_color, + candidate_text_color: UIStyle.candidate_text_color, + candidate_back_color: UIStyle.candidate_back_color, + font_face: UIStyle.font_face, + font_point: UIStyle.font_point, + }) + } + return result + } + + GetActiveColorScheme() { + global rime + if !config := this.api.settings_get_config(this.settings) + return "" + if !rime || !value := rime.config_get_cstring(config, "style/color_scheme") + return "" + return value + } + + SelectColorScheme(color_scheme_id) { + this.api.customize_string(this.settings, "style/color_scheme", color_scheme_id) + return true + } +} + +class UIStyleSettingsDialog extends Gui { + __New(settings, result) { + super.__New("-MaximizeBox -MinimizeBox", "【玉兔毫】界面风格设定", this) + this.settings := settings + this.loaded := false + this.api := RimeLeversApi() + + this.preset := [] + this.result := result + + ; Layout + this.MarginX := 15 + this.MarginY := 15 + this.color_schemes_width := 220 + this.preview_width := 220 + this.preview_offset := 20 + this.AddText("x10 y10", "主题:").GetPos(, , , &h) + this.title_height := h + this.color_schemes := this.AddListBox(Format("Section r15 w{} -Multi", this.color_schemes_width)) + this.color_schemes.OnEvent("Change", (ctrl, info) => this.OnColorSchemeSelChange()) + this.color_schemes.GetPos(, , , &h) + this.list_height := h + this.AddGroupBox(Format("x+{} yp-8 w{} h{}", this.preview_offset, this.preview_width, this.list_height + 8), "预览") + ; 0xE(SS_BITMAP) or 0x4E (Bitmap and Resizable, but text is unclear) + this.preview_img := this.AddPicture("xp+50 yp+50 w180 h180 0xE BackgroundWhite") + + this.set_font := this.AddButton(Format("xs ys+{} w120", this.list_height + this.MarginY), "设置字体") + this.set_font.Opt("+Disabled") ; TODO: implement font setting + this.ok := this.AddButton("x+180 w90", "中") + this.ok.OnEvent("Click", (*) => this.OnOK()) + + this.Populate() + } + + Populate() { + if !this.settings + return + local active := this.settings.GetActiveColorScheme() + local active_index := 0 + this.preset := this.settings.GetPresetColorSchemes() + local names := [] + for i, info in this.preset { + names.Push(info.name) + if info.color_scheme_id = active + active_index := i + } + this.color_schemes.Opt("-Redraw") + this.color_schemes.Add(names) + this.color_schemes.Opt("+Redraw") + if active_index > 0 { + this.color_schemes.Choose(active_index) + this.Preview(active_index) + } + this.loaded := true + } + + OnColorSchemeSelChange() { + local index := this.color_schemes.Value + if index > 0 && index <= this.preset.Length { + this.settings.SelectColorScheme(this.preset[index].color_scheme_id) + this.Preview(index) + } + return 0 + } + + Preview(index) { + if index <= 0 || index > this.preset.Length + return + local info := this.preset[index] + local candidate_box := CandidatePreview(this.preview_img, info, &box_width, &box_height) + box_width := box_width / candidate_box.dpiSacle + box_height := box_height / candidate_box.dpiSacle + local box_x := this.MarginX + this.color_schemes_width + this.preview_offset + Round((this.preview_width - box_width) / 2) + local box_y := this.MarginY + this.title_height + 8 + Round((this.list_height - box_height) / 2) + this.preview_img.Move(box_x, box_y, box_width, box_height) + candidate_box.Render(["输入法", "输入", "数", "书", "输"], 1) + } + + OnOK() { + this.Exit(true) + } + + Exit(yes) { + this.result.yes := yes + this.Destroy() + } +} From 12f2b613149b9ae4287e04163c296d8ea2f70991 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sun, 31 Aug 2025 11:19:49 +0800 Subject: [PATCH 21/38] skip checking registry user can create a file ".portable" in the script dir to force using "Rime" dir in the script dir --- Lib/RabbitCommon.ahk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/RabbitCommon.ahk b/Lib/RabbitCommon.ahk index d69110d..67afc9e 100644 --- a/Lib/RabbitCommon.ahk +++ b/Lib/RabbitCommon.ahk @@ -105,6 +105,8 @@ CreateTraits() { } RabbitUserDataPath() { + if FileExist(A_ScriptDir . "\.portable") + return A_ScriptDir . "\Rime" try { local dir := RegRead("HKEY_CURRENT_USER\Software\Rime\Rabbit", "RimeUserDir") } From 55b264b722e0d7ade6ba35ae9f987ee172a5411a Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Mon, 1 Sep 2025 13:00:44 +0800 Subject: [PATCH 22/38] fix(box): convert em to pt --- Lib/RabbitCandidateBox.ahk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index b05741f..2208b2d 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -21,7 +21,6 @@ #Include #Include -global LVM_GETCOLUMNWIDTH := 0x101D ; https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles global WS_EX_NOACTIVATE := "+E0x8000000" global WS_EX_COMPOSITED := "+E0x02000000" @@ -104,8 +103,9 @@ class CandidateBox { this.candsInfoArray := [] this.commentsInfoArray := [] + local em2pt := 96.0 / 72.0 this.hFamily := Gdip_FontFamilyCreate(this.fontName) - this.hFont := Gdip_FontCreate(this.hFamily, this.fontSize * this.dpiSacle, regular := 0) + this.hFont := Gdip_FontCreate(this.hFamily, this.fontSize * em2pt * this.dpiSacle, regular := 0) this.hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip Gdip_SetStringFormatAlign(this.hFormat, left := 0) ; left:0, center:1, right:2 From c0a95b7f8071bd585af9e5f353ddbe5d1efd82b2 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Mon, 1 Sep 2025 14:41:34 +0800 Subject: [PATCH 23/38] fix: the spacing between preedit & cands --- Lib/RabbitCandidateBox.ahk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 2208b2d..e27226c 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -204,7 +204,7 @@ class CandidateBox { ; Draw preedit rectShrink := 2 - currentY := this.padding + this.borderWidth + currentY := this.lineSpacing + this.borderWidth prdSelTxtRc := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } @@ -214,7 +214,7 @@ class CandidateBox { Gdip_DeleteBrush(pBrsh_hlSelBg) this.DrawText(this.pGraphics, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) this.DrawText(this.pGraphics, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) - currentY += this.prdSelSize.h + this.lineSpacing + currentY += this.prdHlSelSize.h + this.lineSpacing ; Draw candidates Loop this.num_candidates { From 79d6384ed4fc1bec53783ae72b978ca9aaf80c16 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Mon, 1 Sep 2025 15:16:13 +0800 Subject: [PATCH 24/38] fix: adjust comment position --- Lib/RabbitCandidateBox.ahk | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index e27226c..4f9432d 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -167,7 +167,12 @@ class CandidateBox { Gdip_DeleteGraphics(pGraphics) ReleaseDC(hDC, this.gui.Hwnd) - this.boxWidth := Max((Ceil(maxRowWidth) + this.padding * 2 + this.borderWidth * 2), UIStyle.min_width) + this.commentOffset := 0 + this.boxWidth := Ceil(maxRowWidth) + this.padding * 2 + this.borderWidth * 2 + if this.boxWidth < UIStyle.min_width { + this.commentOffset := UIStyle.min_width - this.boxWidth + this.boxWidth := UIStyle.min_width + } this.boxHeight := Ceil(totalHeight) + this.padding * 2 + this.borderWidth * 2 - Round(this.lineSpacing / 2) calcW := this.boxWidth calcH := this.boxHeight @@ -242,7 +247,7 @@ class CandidateBox { commentW := this.commentsInfoArray[A_Index].w if commentW > 0 { - commentRect := { x: candRect.x + candRect.w + this.commentsInfoArray[A_Index].spacing, y: currentY, w: commentW, h: rowSize.h } + commentRect := { x: candRect.x + candRect.w + this.commentsInfoArray[A_Index].spacing + this.commentOffset, y: currentY, w: commentW, h: rowSize.h } this.DrawText(this.pGraphics, this.commentsInfoArray[A_Index].text, commentRect, commentFg) } From a75847f32d75458594b32e2e870980d6ed1ff4b7 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Mon, 1 Sep 2025 15:21:59 +0800 Subject: [PATCH 25/38] fix: do not draw bg for empty preedit SelTxt --- Lib/RabbitCandidateBox.ahk | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 4f9432d..7586804 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -214,9 +214,11 @@ class CandidateBox { prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } this.DrawText(this.pGraphics, this.prdSelTxt, prdSelTxtRc, this.textColor) - pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) - Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) - Gdip_DeleteBrush(pBrsh_hlSelBg) + if this.prdHlSelTxt { + pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) + Gdip_DeleteBrush(pBrsh_hlSelBg) + } this.DrawText(this.pGraphics, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) this.DrawText(this.pGraphics, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) currentY += this.prdHlSelSize.h + this.lineSpacing From 1e0a6ff192659da642f04d139734a33e673f5cf5 Mon Sep 17 00:00:00 2001 From: zerxmega Date: Mon, 1 Sep 2025 20:10:01 +0800 Subject: [PATCH 26/38] CandidateBox surports mutilple fonts --- Lib/RabbitCandidateBox.ahk | 90 +++++++++++++++++++++----------------- Lib/RabbitThemesUI.ahk | 13 ++++-- 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 7586804..79d8025 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -34,9 +34,9 @@ class CandidateBox { hBitmap := 0 oBitmap := 0 pGraphics := 0 - hFont := 0 - hFamily := 0 - hFormat := 0 + mainFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } + labFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } + commentFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } static isHidden := 1 @@ -67,9 +67,9 @@ class CandidateBox { this.lineSpacing := UIStyle.margin_y this.padding := UIStyle.margin_x - ; only use one font to show - this.fontName := UIStyle.font_face - this.fontSize := UIStyle.font_point + this.mainFontObj := this.CreateFontObj(UIStyle.font_face, UIStyle.font_point) + this.labFontObj := this.CreateFontObj(UIStyle.label_font_face, UIStyle.label_font_point) + this.commentFontObj := this.CreateFontObj(UIStyle.comment_font_face, UIStyle.comment_font_point) ; preedite style this.textColor := UIStyle.text_color @@ -89,6 +89,17 @@ class CandidateBox { this.hlCommentTxtColor := UIStyle.hilited_comment_text_color } + CreateFontObj(name, size) { + local em2pt := 96.0 / 72.0 + hFamily := Gdip_FontFamilyCreate(name) + hFont := Gdip_FontCreate(hFamily, size * em2pt * this.dpiSacle, regular := 0) + hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip + Gdip_SetStringFormatAlign(hFormat, left := 0) ; left:0, center:1, right:2 + ; vertical align(top:0, center:1, bottom:2) + DllCall("gdiplus\GdipSetStringFormatLineAlign", "ptr", hFormat, "int", vCenter := 1) + return { hFamily: hFamily, hFont: hFont, hFormat: hFormat } + } + Build(context, &calcW, &calcH) { local menu := context.menu local cands := menu.candidates @@ -103,20 +114,14 @@ class CandidateBox { this.candsInfoArray := [] this.commentsInfoArray := [] - local em2pt := 96.0 / 72.0 - this.hFamily := Gdip_FontFamilyCreate(this.fontName) - this.hFont := Gdip_FontCreate(this.hFamily, this.fontSize * em2pt * this.dpiSacle, regular := 0) - this.hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip - Gdip_SetStringFormatAlign(this.hFormat, left := 0) ; left:0, center:1, right:2 - local hDC := GetDC(this.gui.Hwnd) local pGraphics := Gdip_GraphicsFromHDC(hDC) CreateRectF(&RC, 0, 0, 0, 0) ; Measure preedit texts - this.prdSelSize := this.MeasureString(pGraphics, this.prdSelTxt, this.hFont, this.hFormat, &RC) - this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.hFont, this.hFormat, &RC) - this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.hFont, this.hFormat, &RC) + this.prdSelSize := this.MeasureString(pGraphics, this.prdSelTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) + this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) + this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) ; Measure candidate texts this.candRowSizes := [] @@ -134,23 +139,23 @@ class CandidateBox { else if A_Index <= num_select_keys labelText := SubStr(select_keys, A_Index, 1) labelText := Format(UIStyle.label_format, labelText) - labelInfo := this.MeasureString(pGraphics, labelText, this.hFont, this.hFormat, &RC) + labelInfo := this.MeasureString(pGraphics, labelText, this.labFontObj.hFont, this.labFontObj.hFormat, &RC) labelInfo.text := labelText this.labelsInfoArray.Push(labelInfo) candText := cands[A_Index].text - candInfo := this.MeasureString(pGraphics, candText, this.hFont, this.hFormat, &RC) + candInfo := this.MeasureString(pGraphics, candText, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) candInfo.text := candText this.candsInfoArray.Push(candInfo) commentText := cands[A_Index].comment - commentInfo := this.MeasureString(pGraphics, commentText, this.hFont, this.hFormat, &RC) + commentInfo := this.MeasureString(pGraphics, commentText, this.commentFontObj.hFont, this.commentFontObj.hFormat, &RC) commentInfo.text := commentText this.commentsInfoArray.Push(commentInfo) rowSize := { w: labelInfo.w + candInfo.w + (commentText ? this.padding * 2 + commentInfo.w : 0), - h: candInfo.h + h: Max(labelInfo.h, candInfo.h, commentInfo.h) } this.candRowSizes.Push(rowSize) if (rowSize.w > maxRowWidth) { @@ -213,14 +218,14 @@ class CandidateBox { prdSelTxtRc := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } - this.DrawText(this.pGraphics, this.prdSelTxt, prdSelTxtRc, this.textColor) + this.DrawText(this.pGraphics, this.mainFontObj, this.prdSelTxt, prdSelTxtRc, this.textColor) if this.prdHlSelTxt { pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) Gdip_DeleteBrush(pBrsh_hlSelBg) } - this.DrawText(this.pGraphics, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) - this.DrawText(this.pGraphics, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) + this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) + this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) currentY += this.prdHlSelSize.h + this.lineSpacing ; Draw candidates @@ -244,13 +249,13 @@ class CandidateBox { labelRect := { x: this.padding + this.borderWidth, y: currentY, w: this.labelsInfoArray[A_Index].w, h: rowSize.h } candRect := { x: labelRect.x + labelRect.w, y: currentY, w: this.candsInfoArray[A_Index].w, h: rowSize.h } - this.DrawText(this.pGraphics, this.labelsInfoArray[A_Index].text, labelRect, labelFg) - this.DrawText(this.pGraphics, this.candsInfoArray[A_Index].text, candRect, candFg) + this.DrawText(this.pGraphics, this.labFontObj, this.labelsInfoArray[A_Index].text, labelRect, labelFg) + this.DrawText(this.pGraphics, this.mainFontObj, this.candsInfoArray[A_Index].text, candRect, candFg) commentW := this.commentsInfoArray[A_Index].w if commentW > 0 { commentRect := { x: candRect.x + candRect.w + this.commentsInfoArray[A_Index].spacing + this.commentOffset, y: currentY, w: commentW, h: rowSize.h } - this.DrawText(this.pGraphics, this.commentsInfoArray[A_Index].text, commentRect, commentFg) + this.DrawText(this.pGraphics, this.commentFontObj, this.commentsInfoArray[A_Index].text, commentRect, commentFg) } currentY += rowSize.h + this.lineSpacing @@ -268,13 +273,19 @@ class CandidateBox { } } - ReleaseFont() { - if (this.hFont) - Gdip_DeleteFont(this.hFont) - if (this.hFamily) - Gdip_DeleteFontFamily(this.hFamily) - if (this.hFormat) - Gdip_DeleteStringFormat(this.hFormat) + ReleaseFonts() { + DeleteFont(this.mainFontObj) + DeleteFont(this.labFontObj) + DeleteFont(this.commentFontObj) + + DeleteFont(oFnt) { + if (oFnt.hFont) + Gdip_DeleteFont(oFnt.hFont), oFnt.hFont := 0 + if (oFnt.hFamily) + Gdip_DeleteFontFamily(oFnt.hFamily), oFnt.hFamily := 0 + if (oFnt.hFormat) + Gdip_DeleteStringFormat(oFnt.hFormat), oFnt.hFormat := 0 + } } ReleaseDrawingSurface() { @@ -282,19 +293,16 @@ class CandidateBox { Gdip_DeleteGraphics(this.pGraphics) this.pGraphics := 0 } - if (this.hBitmap) { - DeleteObject(this.hBitmap) - this.hBitmap := 0 - } - if (this.hDC) { - SelectObject(this.hDC, this.oBitmap) + if (this.hDC && this.hBitmap) { + SelectObject(this.hDC, this.oBitmap), DeleteObject(this.hBitmap) DeleteDC(this.hDC) + this.oBitmap := 0, this.hBitmap := 0 this.hDC := 0 } } ReleaseAll() { - this.ReleaseFont() + this.ReleaseFonts() this.ReleaseDrawingSurface() if (this.pToken) { @@ -331,10 +339,10 @@ class CandidateBox { return { w: NumGet(rc.Ptr, 8, "Float"), h: NumGet(rc.Ptr, 12, "Float") } } - DrawText(pGraphics, text, textRect, color) { + DrawText(pGraphics, fontObj, text, textRect, color) { this.pBrush := Gdip_BrushCreateSolid(color) CreateRectF(&RC, textRect.x, textRect.y, textRect.w, textRect.h) - Gdip_DrawString(pGraphics, text, this.hFont, this.hFormat, this.pBrush, &RC) + Gdip_DrawString(pGraphics, text, fontObj.hFont, fontObj.hFormat, this.pBrush, &RC) Gdip_DeleteBrush(this.pBrush) } diff --git a/Lib/RabbitThemesUI.ahk b/Lib/RabbitThemesUI.ahk index 3be338c..13a0b9b 100644 --- a/Lib/RabbitThemesUI.ahk +++ b/Lib/RabbitThemesUI.ahk @@ -25,6 +25,7 @@ class CandidatePreview { hBitmap := 0 pGraphics := 0 hFont := 0 + hFamily := 0 hFormat := 0 __New(ctrl, theme, &calcW, &calcH) { @@ -154,12 +155,18 @@ class CandidatePreview { } ReleaseFont() { - if (this.hFont) + if (this.hFont) { Gdip_DeleteFont(this.hFont) - if (this.hFamily) + this.hFont := 0 + } + if (this.hFamily) { Gdip_DeleteFontFamily(this.hFamily) - if (this.hFormat) + this.hFamily := 0 + } + if (this.hFormat) { Gdip_DeleteStringFormat(this.hFormat) + this.hFormat := 0 + } } ReleaseDrawingSurface() { From 3c329d958cd6ad4a76c7ebd9372f0b4639becd8f Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 2 Sep 2025 10:45:28 +0800 Subject: [PATCH 27/38] fix: strange candidate height when no comments --- Lib/RabbitCandidateBox.ahk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 79d8025..821b72a 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -126,7 +126,7 @@ class CandidateBox { ; Measure candidate texts this.candRowSizes := [] maxRowWidth := this.prdSelSize.w + this.padding + this.prdHlSelSize.w + this.prdHlUnselSize.w - totalHeight := this.prdHlSelSize.h + this.lineSpacing + totalHeight := Max(this.prdSelSize.h, this.prdHlSelSize.h, this.prdHlUnselSize.h) + this.lineSpacing has_label := !!context.select_labels[0] select_keys := menu.select_keys @@ -226,7 +226,7 @@ class CandidateBox { } this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) - currentY += this.prdHlSelSize.h + this.lineSpacing + currentY += Max(this.prdSelSize.h, this.prdHlSelSize.h, this.prdHlUnselSize.h) + this.lineSpacing ; Draw candidates Loop this.num_candidates { @@ -316,7 +316,7 @@ class CandidateBox { MeasureString(pGraphics, text, hFont, hFormat, &RectF) { if !text - return { w: 0, h: 32 } + return { w: 0, h: 0 } rc := Buffer(16) ; !Notice, this way gets incorrect dim in test From 99e973c84247e598b6c5a88ba81ce07cee56a758 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 2 Sep 2025 13:54:44 +0800 Subject: [PATCH 28/38] chore: add nightly build --- .github/workflows/ci.yaml | 33 +++++++++++++++++++++++++++++++++ README.md | 10 +++------- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b923154..68b3715 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -191,6 +191,39 @@ jobs: README.md rime-install.bat + create-nightly: + strategy: + matrix: + target: [ x86, x64 ] + name: Create Nightly release + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + needs: build-rabbit + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + name: Rabbit-Full-${{ matrix.target }} + path: release + + - name: Pack Zip + working-directory: release + run: | + mkdir Rime && zip -r -q ../rabbit-nightly-${{ matrix.target }}.zip * + + - name: Upload Nightly + uses: marvinpinto/action-automatic-releases@latest + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: latest + prerelease: true + title: "Nightly" + files: | + rabbit-nightly-${{ matrix.target }}.zip + create-release: name: Create Release if: startsWith(github.ref, 'refs/tags/v') diff --git a/README.md b/README.md index 917e9c2..8fa0bfc 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ ## 下载体验 > [!NOTE] -> 发现程序漏洞请在 [Issues](https://github.com/rimeinn/rabbit/issues/new/choose) 反馈。 -> 使用问题可以在 [Discussions](https://github.com/rimeinn/rabbit/discussions) 讨论, -> 或者加入 [Telegram 群聊](https://t.me/rime_rabbit)。 +> 发现程序漏洞请在 [Issues](https://github.com/rimeinn/rabbit/issues/new/choose) 反馈。使用问题可以在 [Discussions](https://github.com/rimeinn/rabbit/discussions) 讨论,或者加入 [Telegram 群聊](https://t.me/rime_rabbit)。 ### Release 版 @@ -26,11 +24,9 @@ scoop bucket add siku https://github.com/amorphobia/siku scoop install siku/rabbit ``` -### Action 版 +### 每夜构建版本 (Nightly) -需要先登录你的 GitHub 账号。 - -前往 [Actions 页面](https://github.com/rimeinn/rabbit/actions) 找到最近成功构建的一次,在生成的 Artifacts 中点击 `Rabbit-Full` 下载,将压缩包内容解压到一个新建目录中,运行 `Rabbit.exe` 即可。之后更新时,可只下载 `Rabbit` 或 `Data` 覆盖相应的文件。 +每夜构建版本可在 [`latest` 标签](https://github.com/rimeinn/rabbit/releases/tag/latest)页面下载。 ## 脚本编译 From e1bf0550ece092e57bf65cdae30beaf588a1cc34 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 2 Sep 2025 14:06:09 +0800 Subject: [PATCH 29/38] chore: upload nightly for both x64 & x86 --- .github/workflows/ci.yaml | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 68b3715..67dbf9b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -192,9 +192,6 @@ jobs: rime-install.bat create-nightly: - strategy: - matrix: - target: [ x86, x64 ] name: Create Nightly release if: ${{ github.ref == 'refs/heads/master' }} runs-on: ubuntu-latest @@ -203,16 +200,27 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Download Artifacts + - name: Download x64 uses: actions/download-artifact@v4 with: - name: Rabbit-Full-${{ matrix.target }} - path: release + name: Rabbit-Full-x64 + path: x64 - - name: Pack Zip - working-directory: release + - name: Pack x64 + working-directory: x64 + run: | + mkdir Rime && zip -r -q ../rabbit-nightly-x64.zip * + + - name: Download x86 + uses: actions/download-artifact@v4 + with: + name: Rabbit-Full-x86 + path: x86 + + - name: Pack x86 + working-directory: x86 run: | - mkdir Rime && zip -r -q ../rabbit-nightly-${{ matrix.target }}.zip * + mkdir Rime && zip -r -q ../rabbit-nightly-x86.zip * - name: Upload Nightly uses: marvinpinto/action-automatic-releases@latest @@ -222,7 +230,8 @@ jobs: prerelease: true title: "Nightly" files: | - rabbit-nightly-${{ matrix.target }}.zip + rabbit-nightly-x64.zip + rabbit-nightly-x86.zip create-release: name: Create Release From 05382b534461cd04ddcd6b0f96139142e52c1bd9 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 2 Sep 2025 15:58:37 +0800 Subject: [PATCH 30/38] fix: remove extra spacing at end --- Lib/RabbitCandidateBox.ahk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 821b72a..f421354 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -161,8 +161,9 @@ class CandidateBox { if (rowSize.w > maxRowWidth) { maxRowWidth := rowSize.w } - totalHeight += candInfo.h + this.lineSpacing + totalHeight += rowSize.h + this.lineSpacing } + totalHeight -= this.lineSpacing ; remove extra line spacing ; get better spacing to align comments Loop this.num_candidates { From 238f7d400a893b502e20295c6e09831eb6df1b98 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Wed, 3 Sep 2025 12:30:17 +0800 Subject: [PATCH 31/38] add option to use legacy candidate box (issue #21) --- Lib/RabbitCandidateBox.ahk | 166 ++++++++++++++++++------------------- Lib/RabbitConfig.ahk | 3 + Rabbit.ahk | 5 +- 3 files changed, 89 insertions(+), 85 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index f421354..143b8cd 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -356,11 +356,10 @@ class CandidateBox { } } -/* -class CandidateBox { +class LegacyCandidateBox { static dbg := false static gui := 0 - static border := CandidateBox.dbg ? "+border" : 0 + static border := LegacyCandidateBox.dbg ? "+border" : 0 __New() { this.UpdateUIStyle() @@ -371,67 +370,67 @@ class CandidateBox { del_opaque(color) { return color & 0xffffff } - CandidateBox.text_color := del_opaque(UIStyle.text_color) - CandidateBox.back_color := del_opaque(UIStyle.back_color) - CandidateBox.candidate_text_color := del_opaque(UIStyle.candidate_text_color) - CandidateBox.candidate_back_color := del_opaque(UIStyle.candidate_back_color) - CandidateBox.label_color := del_opaque(UIStyle.label_color) - CandidateBox.comment_text_color := del_opaque(UIStyle.comment_text_color) - CandidateBox.hilited_text_color := del_opaque(UIStyle.hilited_text_color) - CandidateBox.hilited_back_color := del_opaque(UIStyle.hilited_back_color) - CandidateBox.hilited_candidate_text_color := del_opaque(UIStyle.hilited_candidate_text_color) - CandidateBox.hilited_candidate_back_color := del_opaque(UIStyle.hilited_candidate_back_color) - CandidateBox.hilited_label_color := del_opaque(UIStyle.hilited_label_color) - CandidateBox.hilited_comment_text_color := del_opaque(UIStyle.hilited_comment_text_color) - - CandidateBox.base_opt := Format("c{:x} Background{:x} {}", CandidateBox.text_color, CandidateBox.back_color, CandidateBox.border) - CandidateBox.candidate_opt := Format("c{:x} Background{:x} {}", CandidateBox.candidate_text_color, CandidateBox.candidate_back_color, CandidateBox.border) - CandidateBox.label_opt := Format("c{:x} Background{:x} {}", CandidateBox.label_color, CandidateBox.candidate_back_color, CandidateBox.border) - CandidateBox.comment_opt := Format("c{:x} Background{:x} {}", CandidateBox.comment_text_color, CandidateBox.candidate_back_color, CandidateBox.border) - CandidateBox.hilited_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_text_color, CandidateBox.hilited_back_color, CandidateBox.border) - CandidateBox.hilited_candidate_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_candidate_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) - CandidateBox.hilited_label_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_label_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) - CandidateBox.hilited_comment_opt := Format("c{:x} Background{:x} {}", CandidateBox.hilited_comment_text_color, CandidateBox.hilited_candidate_back_color, CandidateBox.border) - - CandidateBox.base_font_opt := Format("s{} q5", UIStyle.font_point) - CandidateBox.label_font_opt := Format("s{} q5", UIStyle.label_font_point) - CandidateBox.comment_font_opt := Format("s{} q5", UIStyle.comment_font_point) - - if CandidateBox.gui { - CandidateBox.gui.BackColor := CandidateBox.back_color - CandidateBox.gui.MarginX := UIStyle.margin_x - CandidateBox.gui.MarginY := UIStyle.margin_y - - if HasProp(CandidateBox.gui, "pre") && CandidateBox.gui.pre - CandidateBox.gui.pre.Opt(CandidateBox.base_opt) - if HasProp(CandidateBox.gui, "sel") && CandidateBox.gui.sel - CandidateBox.gui.sel.Opt(CandidateBox.hilited_opt) - if HasProp(CandidateBox.gui, "post") && CandidateBox.gui.post - CandidateBox.gui.post.Opt(CandidateBox.base_opt) + LegacyCandidateBox.text_color := del_opaque(UIStyle.text_color) + LegacyCandidateBox.back_color := del_opaque(UIStyle.back_color) + LegacyCandidateBox.candidate_text_color := del_opaque(UIStyle.candidate_text_color) + LegacyCandidateBox.candidate_back_color := del_opaque(UIStyle.candidate_back_color) + LegacyCandidateBox.label_color := del_opaque(UIStyle.label_color) + LegacyCandidateBox.comment_text_color := del_opaque(UIStyle.comment_text_color) + LegacyCandidateBox.hilited_text_color := del_opaque(UIStyle.hilited_text_color) + LegacyCandidateBox.hilited_back_color := del_opaque(UIStyle.hilited_back_color) + LegacyCandidateBox.hilited_candidate_text_color := del_opaque(UIStyle.hilited_candidate_text_color) + LegacyCandidateBox.hilited_candidate_back_color := del_opaque(UIStyle.hilited_candidate_back_color) + LegacyCandidateBox.hilited_label_color := del_opaque(UIStyle.hilited_label_color) + LegacyCandidateBox.hilited_comment_text_color := del_opaque(UIStyle.hilited_comment_text_color) + + LegacyCandidateBox.base_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.text_color, LegacyCandidateBox.back_color, LegacyCandidateBox.border) + LegacyCandidateBox.candidate_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.candidate_text_color, LegacyCandidateBox.candidate_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.label_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.label_color, LegacyCandidateBox.candidate_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.comment_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.comment_text_color, LegacyCandidateBox.candidate_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.hilited_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.hilited_text_color, LegacyCandidateBox.hilited_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.hilited_candidate_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.hilited_candidate_text_color, LegacyCandidateBox.hilited_candidate_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.hilited_label_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.hilited_label_color, LegacyCandidateBox.hilited_candidate_back_color, LegacyCandidateBox.border) + LegacyCandidateBox.hilited_comment_opt := Format("c{:x} Background{:x} {}", LegacyCandidateBox.hilited_comment_text_color, LegacyCandidateBox.hilited_candidate_back_color, LegacyCandidateBox.border) + + LegacyCandidateBox.base_font_opt := Format("s{} q5", UIStyle.font_point) + LegacyCandidateBox.label_font_opt := Format("s{} q5", UIStyle.label_font_point) + LegacyCandidateBox.comment_font_opt := Format("s{} q5", UIStyle.comment_font_point) + + if LegacyCandidateBox.gui { + LegacyCandidateBox.gui.BackColor := LegacyCandidateBox.back_color + LegacyCandidateBox.gui.MarginX := UIStyle.margin_x + LegacyCandidateBox.gui.MarginY := UIStyle.margin_y + + if HasProp(LegacyCandidateBox.gui, "pre") && LegacyCandidateBox.gui.pre + LegacyCandidateBox.gui.pre.Opt(LegacyCandidateBox.base_opt) + if HasProp(LegacyCandidateBox.gui, "sel") && LegacyCandidateBox.gui.sel + LegacyCandidateBox.gui.sel.Opt(LegacyCandidateBox.hilited_opt) + if HasProp(LegacyCandidateBox.gui, "post") && LegacyCandidateBox.gui.post + LegacyCandidateBox.gui.post.Opt(LegacyCandidateBox.base_opt) } } - Build(&context, &width, &height) { - if !CandidateBox.gui || !CandidateBox.gui.built - CandidateBox.gui := CandidateBox.BoxGui(&context) + Build(context, &width, &height) { + if !LegacyCandidateBox.gui || !LegacyCandidateBox.gui.built + LegacyCandidateBox.gui := LegacyCandidateBox.BoxGui(context) else - CandidateBox.gui.Update(&context) - width := CandidateBox.gui.max_width - height := CandidateBox.gui.max_height + LegacyCandidateBox.gui.Update(context) + width := LegacyCandidateBox.gui.max_width + height := LegacyCandidateBox.gui.max_height } Show(x, y) { - CandidateBox.gui.Show(Format("AutoSize NA x{} y{}", x, y)) + LegacyCandidateBox.gui.Show(Format("AutoSize NA x{} y{}", x, y)) } Hide() { - if CandidateBox.gui && HasMethod(CandidateBox.gui, "Show") - CandidateBox.gui.Show("Hide") + if LegacyCandidateBox.gui && HasMethod(LegacyCandidateBox.gui, "Show") + LegacyCandidateBox.gui.Show("Hide") } class BoxGui extends Gui { built := false - __New(&context, &pre?, &sel?, &post?, &menu?) { + __New(context, &pre?, &sel?, &post?, &menu?) { super.__New(, , this) menu := context.menu @@ -439,11 +438,11 @@ class CandidateBox { local num_candidates := menu.num_candidates local hilited_index := menu.highlighted_candidate_index + 1 local composition := context.composition - GetCompositionText(&composition, &pre, &sel, &post) + GetCompositionText(composition, &pre, &sel, &post) this.Opt(Format("-DPIScale -Caption +Owner +AlwaysOnTop {} {} {}", WS_EX_NOACTIVATE, WS_EX_COMPOSITED, WS_EX_LAYERED)) - this.BackColor := CandidateBox.back_color - this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + this.BackColor := LegacyCandidateBox.back_color + this.SetFont(LegacyCandidateBox.base_font_opt, UIStyle.font_face) this.MarginX := UIStyle.margin_x this.MarginY := UIStyle.margin_y this.num_candidates := num_candidates @@ -452,12 +451,12 @@ class CandidateBox { ; build preedit this.max_width := 0 this.preedit_height := 0 - local head_position := Format("x{} y{} section {}", this.MarginX, this.MarginY, CandidateBox.border) + local head_position := Format("x{} y{} section {}", this.MarginX, this.MarginY, LegacyCandidateBox.border) local position := head_position if pre { this.pre := this.AddText(position, pre) - this.pre.Opt(CandidateBox.base_opt) - position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + this.pre.Opt(LegacyCandidateBox.base_opt) + position := Format("x+{} ys {}", this.MarginX, LegacyCandidateBox.border) this.pre.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) this.pre_width := w @@ -465,8 +464,8 @@ class CandidateBox { } if sel { this.sel := this.AddText(position, sel) - this.sel.Opt(CandidateBox.hilited_opt) - position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) + this.sel.Opt(LegacyCandidateBox.hilited_opt) + position := Format("x+{} ys {}", this.MarginX, LegacyCandidateBox.border) this.sel.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) this.sel_width := w @@ -474,7 +473,7 @@ class CandidateBox { } if post { this.post := this.AddText(position, post) - this.post.Opt(CandidateBox.base_opt) + this.post.Opt(LegacyCandidateBox.base_opt) this.post.GetPos(, , &w, &h) this.preedit_height := max(this.preedit_height, h) this.post_width := w @@ -490,42 +489,42 @@ class CandidateBox { local select_keys := menu.select_keys local num_select_keys := StrLen(select_keys) loop num_candidates { - position := Format("xs y+{} section {}", this.MarginY, CandidateBox.border) + position := Format("xs y+{} section {}", this.MarginY, LegacyCandidateBox.border) local label_text := String(A_Index) if A_Index <= menu.page_size && has_label label_text := context.select_labels[A_Index] else if A_Index <= num_select_keys label_text := SubStr(select_keys, A_Index, 1) label_text := Format(UIStyle.label_format, label_text) - this.SetFont(CandidateBox.label_font_opt, UIStyle.label_font_face) + this.SetFont(LegacyCandidateBox.label_font_opt, UIStyle.label_font_face) local label := this.AddText(Format("Right {} vL{}", position, A_Index), label_text) label.GetPos(, , &w, &h1) this.max_label_width := max(this.max_label_width, w + this.MarginX) - position := Format("x+{} ys {}", this.MarginX, CandidateBox.border) - this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + position := Format("x+{} ys {}", this.MarginX, LegacyCandidateBox.border) + this.SetFont(LegacyCandidateBox.base_font_opt, UIStyle.font_face) local candidate := this.AddText(Format("{} vC{}", position, A_Index), cands[A_Index].text) candidate.GetPos(, , &w, &h2) this.max_candidate_width := max(this.max_candidate_width, w + this.MarginX) if comment_text := cands[A_Index].comment this.has_comment := true - this.SetFont(CandidateBox.comment_font_opt, UIStyle.comment_font_face) + this.SetFont(LegacyCandidateBox.comment_font_opt, UIStyle.comment_font_face) local comment := this.AddText(Format("{} vM{}", position, A_Index), comment_text) comment.GetPos(, , &w, &h3) - comment.Opt(Format("c{:x}", CandidateBox.comment_text_color)) + comment.Opt(Format("c{:x}", LegacyCandidateBox.comment_text_color)) comment.Visible := this.has_comment this.max_comment_width := max(this.max_comment_width, w) this.candidate_height := max(this.candidate_height, h1, h2, h3) if A_Index == hilited_index { - label.Opt(CandidateBox.hilited_label_opt) - candidate.Opt(CandidateBox.hilited_candidate_opt) - comment.Opt(CandidateBox.hilited_comment_opt) + label.Opt(LegacyCandidateBox.hilited_label_opt) + candidate.Opt(LegacyCandidateBox.hilited_candidate_opt) + comment.Opt(LegacyCandidateBox.hilited_comment_opt) } else { - label.Opt(CandidateBox.label_opt) - candidate.Opt(CandidateBox.candidate_opt) - comment.Opt(CandidateBox.comment_opt) + label.Opt(LegacyCandidateBox.label_opt) + candidate.Opt(LegacyCandidateBox.candidate_opt) + comment.Opt(LegacyCandidateBox.comment_opt) } } @@ -562,11 +561,11 @@ class CandidateBox { this.built := true } - Update(&context) { - local fake_gui := CandidateBox.BoxGui(&context, &pre, &sel, &post, &menu) + Update(context) { + local fake_gui := LegacyCandidateBox.BoxGui(context, &pre, &sel, &post, &menu) local num_candidates := menu.num_candidates local hilited_index := menu.highlighted_candidate_index + 1 - this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + this.SetFont(LegacyCandidateBox.base_font_opt, UIStyle.font_face) this.num_candidates := max(this.num_candidates, num_candidates) this.max_width := fake_gui.max_width this.max_height := fake_gui.max_height @@ -611,17 +610,17 @@ class CandidateBox { local fake_label := fake_gui["L" . A_Index] local fake_candidate := fake_gui["C" . A_Index] local fake_comment := fake_gui["M" . A_Index] - this.SetFont(CandidateBox.label_font_opt, UIStyle.label_font_face) + this.SetFont(LegacyCandidateBox.label_font_opt, UIStyle.label_font_face) try local label := this["L" . A_Index] catch local label := this.AddText(Format("vL{}", A_Index), fake_label.Value) - this.SetFont(CandidateBox.base_font_opt, UIStyle.font_face) + this.SetFont(LegacyCandidateBox.base_font_opt, UIStyle.font_face) try local candidate := this["C" . A_Index] catch local candidate := this.AddText(Format("vC{}", A_Index), fake_candidate.Value) - this.SetFont(CandidateBox.comment_font_opt, UIStyle.comment_font_face) + this.SetFont(LegacyCandidateBox.comment_font_opt, UIStyle.comment_font_face) try local comment := this["M" . A_Index] catch @@ -637,13 +636,13 @@ class CandidateBox { comment.Move(x, y, w, h) if A_Index == hilited_index { - label.Opt(CandidateBox.hilited_label_opt) - candidate.Opt(CandidateBox.hilited_candidate_opt) - comment.Opt(CandidateBox.hilited_comment_opt) + label.Opt(LegacyCandidateBox.hilited_label_opt) + candidate.Opt(LegacyCandidateBox.hilited_candidate_opt) + comment.Opt(LegacyCandidateBox.hilited_comment_opt) } else { - label.Opt(CandidateBox.label_opt) - candidate.Opt(CandidateBox.candidate_opt) - comment.Opt(CandidateBox.comment_opt) + label.Opt(LegacyCandidateBox.label_opt) + candidate.Opt(LegacyCandidateBox.candidate_opt) + comment.Opt(LegacyCandidateBox.comment_opt) } local visible := (A_Index <= num_candidates) label.Visible := visible @@ -656,7 +655,6 @@ class CandidateBox { } } } -*/ GetCompositionText(composition, &pre_selected, &selected, &post_selected) { pre_selected := "" diff --git a/Lib/RabbitConfig.ahk b/Lib/RabbitConfig.ahk index 056d96a..f5beaca 100644 --- a/Lib/RabbitConfig.ahk +++ b/Lib/RabbitConfig.ahk @@ -27,6 +27,7 @@ class RabbitConfig { static preset_process_ascii := Map() static schema_icon := Map() static fix_candidate_box := false + static use_legacy_candidate_box := false static load() { global rime, IS_DARK_MODE @@ -58,6 +59,8 @@ class RabbitConfig { if rime.config_test_get_bool(config, "fix_candidate_box", &result) RabbitConfig.fix_candidate_box := !!result + if rime.config_test_get_bool(config, "use_legacy_candidate_box", &result) + RabbitConfig.use_legacy_candidate_box := !!result UIStyle.Update(config, true) if IS_DARK_MODE := RabbitIsUserDarkMode() { diff --git a/Rabbit.ahk b/Rabbit.ahk index 2e0c55e..92095d5 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -100,7 +100,10 @@ RabbitMain(args) { CleanOldLogs() RabbitConfig.load() - box := CandidateBox() + if RabbitConfig.use_legacy_candidate_box + box := LegacyCandidateBox() + else + box := CandidateBox() RegisterHotKeys() UpdateStateLabels() if status := rime.get_status(session_id) { From e6fbeee44f2275ae5fecd45923019d5954dfeadb Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 6 Sep 2025 10:15:02 +0800 Subject: [PATCH 32/38] try to fix #21 , add missing emoji font as fallback Squashed commit of the following: commit 2b36cf055044958cd8bea2bd9c24dafd57964cd0 Author: zerxmega Date: Thu Sep 4 18:34:56 2025 +0800 try to fix #21 , add missing emoji font as fallback commit f41102feea3be67dcaddc7eaaf5bbdea1bab10ea Merge: 97a5c16 238f7d4 Author: zerxmega Date: Thu Sep 4 12:09:36 2025 +0800 Merge branch 'master' of github-rawbx:rawbx/rabbit commit 97a5c16652dbc51b6eacea094209f9e311d9fc12 Author: zerxmega Date: Mon Sep 1 20:10:01 2025 +0800 CandidateBox surports mutilple fonts --- Lib/RabbitCandidateBox.ahk | 190 +++++++++++++++++++++++++------------ 1 file changed, 129 insertions(+), 61 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 143b8cd..2aba304 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -34,9 +34,10 @@ class CandidateBox { hBitmap := 0 oBitmap := 0 pGraphics := 0 - mainFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } - labFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } - commentFontObj := { hFamily: 0, hFont: 0, hFormat: 0 } + mainFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } + labFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } + commentFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } + fallbackFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } static isHidden := 1 @@ -67,9 +68,11 @@ class CandidateBox { this.lineSpacing := UIStyle.margin_y this.padding := UIStyle.margin_x - this.mainFontObj := this.CreateFontObj(UIStyle.font_face, UIStyle.font_point) - this.labFontObj := this.CreateFontObj(UIStyle.label_font_face, UIStyle.label_font_point) - this.commentFontObj := this.CreateFontObj(UIStyle.comment_font_face, UIStyle.comment_font_point) + this.mainFontHs := this.CreateFontObj(UIStyle.font_face, UIStyle.font_point) + this.labFontHs := this.CreateFontObj(UIStyle.label_font_face, UIStyle.label_font_point) + this.commentFontHs := this.CreateFontObj(UIStyle.comment_font_face, UIStyle.comment_font_point) + this.fallbackFontHs := this.CreateFontObj("Segoe UI Emoji", UIStyle.font_point) + ; this.symbolFontHs := this.CreateFontObj("Segoe UI Symbol", UIStyle.font_point) ; preedite style this.textColor := UIStyle.text_color @@ -96,7 +99,7 @@ class CandidateBox { hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip Gdip_SetStringFormatAlign(hFormat, left := 0) ; left:0, center:1, right:2 ; vertical align(top:0, center:1, bottom:2) - DllCall("gdiplus\GdipSetStringFormatLineAlign", "ptr", hFormat, "int", vCenter := 1) + DllCall("gdiplus\GdipSetStringFormatLineAlign", "ptr", hFormat, "int", vCenter := 1) return { hFamily: hFamily, hFont: hFont, hFormat: hFormat } } @@ -110,18 +113,21 @@ class CandidateBox { this.prdSelTxt := pre_selected this.prdHlSelTxt := selected this.prdHlUnselTxt := post_selected - this.labelsInfoArray := [] - this.candsInfoArray := [] - this.commentsInfoArray := [] + this.labelsSliceInfo := [] + this.candsSliceInfo := [] + this.commentsSliceInfo := [] local hDC := GetDC(this.gui.Hwnd) local pGraphics := Gdip_GraphicsFromHDC(hDC) CreateRectF(&RC, 0, 0, 0, 0) ; Measure preedit texts - this.prdSelSize := this.MeasureString(pGraphics, this.prdSelTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) - this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) - this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) + baseXOffset := this.padding + this.borderWidth + ; prdSelTxt may contains unicode symbols + this.prdSelTxtSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset, this.prdSelTxt, this.mainFontHs, &textBoxSize, &RC) + this.prdSelSize := textBoxSize + this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.mainFontHs, &RC) + this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.mainFontHs, &RC) ; Measure candidate texts this.candRowSizes := [] @@ -132,6 +138,7 @@ class CandidateBox { select_keys := menu.select_keys num_select_keys := StrLen(select_keys) + ; candidates may contain unicode symbols Loop this.num_candidates { labelText := String(A_Index) if A_Index <= menu.page_size && has_label @@ -139,23 +146,20 @@ class CandidateBox { else if A_Index <= num_select_keys labelText := SubStr(select_keys, A_Index, 1) labelText := Format(UIStyle.label_format, labelText) - labelInfo := this.MeasureString(pGraphics, labelText, this.labFontObj.hFont, this.labFontObj.hFormat, &RC) - labelInfo.text := labelText - this.labelsInfoArray.Push(labelInfo) + labelSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset, labelText, this.labFontHs, &labelBoxSize, &RC) + this.labelsSliceInfo.Push({ w: labelBoxSize.w, h: labelBoxSize.h, sliceInfo: labelSlicedInfo }) candText := cands[A_Index].text - candInfo := this.MeasureString(pGraphics, candText, this.mainFontObj.hFont, this.mainFontObj.hFormat, &RC) - candInfo.text := candText - this.candsInfoArray.Push(candInfo) + candSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset + labelBoxSize.w, candText, this.mainFontHs, &candBoxSize, &RC) + this.candsSliceInfo.Push({ w: candBoxSize.w, h: candBoxSize.h, sliceInfo: candSlicedInfo }) commentText := cands[A_Index].comment - commentInfo := this.MeasureString(pGraphics, commentText, this.commentFontObj.hFont, this.commentFontObj.hFormat, &RC) - commentInfo.text := commentText - this.commentsInfoArray.Push(commentInfo) + commentSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset + labelBoxSize.w + candBoxSize.w, commentText, this.commentFontHs, &commentBoxSize, &RC) + this.commentsSliceInfo.Push({ w: commentBoxSize.w, h: commentBoxSize.h, sliceInfo: commentSlicedInfo }) rowSize := { - w: labelInfo.w + candInfo.w + (commentText ? this.padding * 2 + commentInfo.w : 0), - h: Max(labelInfo.h, candInfo.h, commentInfo.h) + w: labelBoxSize.w + candBoxSize.w + (commentText ? this.padding * 2 + commentBoxSize.w : 0), + h: Max(labelBoxSize.h, candBoxSize.h, commentBoxSize.h) } this.candRowSizes.Push(rowSize) if (rowSize.w > maxRowWidth) { @@ -165,11 +169,6 @@ class CandidateBox { } totalHeight -= this.lineSpacing ; remove extra line spacing - ; get better spacing to align comments - Loop this.num_candidates { - this.commentsInfoArray[A_Index].spacing := maxRowWidth - this.labelsInfoArray[A_Index].w - this.candsInfoArray[A_Index].w - this.commentsInfoArray[A_Index].w - } - Gdip_DeleteGraphics(pGraphics) ReleaseDC(hDC, this.gui.Hwnd) @@ -182,6 +181,16 @@ class CandidateBox { this.boxHeight := Ceil(totalHeight) + this.padding * 2 + this.borderWidth * 2 - Round(this.lineSpacing / 2) calcW := this.boxWidth calcH := this.boxHeight + + ; get better spacing to align comments + loop this.num_candidates { + commentW := this.commentsSliceInfo[A_Index].w + if commentW > 0 { + alignCommentGap := maxRowWidth - this.labelsSliceInfo[A_Index].w - this.candsSliceInfo[A_Index].w - commentW + for _, info in this.commentsSliceInfo[A_Index].sliceInfo + info.x := info.x + alignCommentGap + this.commentOffset + } + } } Show(x, y) { @@ -219,14 +228,14 @@ class CandidateBox { prdSelTxtRc := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } - this.DrawText(this.pGraphics, this.mainFontObj, this.prdSelTxt, prdSelTxtRc, this.textColor) + this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, currentY, this.prdSelSize.h, this.prdSelTxtSlicedInfo, this.textColor) if this.prdHlSelTxt { pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) Gdip_DeleteBrush(pBrsh_hlSelBg) } - this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) - this.DrawText(this.pGraphics, this.mainFontObj, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) + this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) + this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) currentY += Max(this.prdSelSize.h, this.prdHlSelSize.h, this.prdHlUnselSize.h) + this.lineSpacing ; Draw candidates @@ -248,15 +257,11 @@ class CandidateBox { Gdip_DeleteBrush(pBrsh_hlCandBg) } - labelRect := { x: this.padding + this.borderWidth, y: currentY, w: this.labelsInfoArray[A_Index].w, h: rowSize.h } - candRect := { x: labelRect.x + labelRect.w, y: currentY, w: this.candsInfoArray[A_Index].w, h: rowSize.h } - this.DrawText(this.pGraphics, this.labFontObj, this.labelsInfoArray[A_Index].text, labelRect, labelFg) - this.DrawText(this.pGraphics, this.mainFontObj, this.candsInfoArray[A_Index].text, candRect, candFg) + this.DrawSlicedTexts(this.pGraphics, this.labFontHs, currentY, rowSize.h, this.labelsSliceInfo[A_Index].sliceInfo, labelFg) + this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, currentY, rowSize.h, this.candsSliceInfo[A_Index].sliceInfo, candFg) - commentW := this.commentsInfoArray[A_Index].w - if commentW > 0 { - commentRect := { x: candRect.x + candRect.w + this.commentsInfoArray[A_Index].spacing + this.commentOffset, y: currentY, w: commentW, h: rowSize.h } - this.DrawText(this.pGraphics, this.commentFontObj, this.commentsInfoArray[A_Index].text, commentRect, commentFg) + if this.commentsSliceInfo[A_Index].w > 0 { + this.DrawSlicedTexts(this.pGraphics, this.commentFontHs, currentY, rowSize.h, this.commentsSliceInfo[A_Index].sliceInfo, commentFg) } currentY += rowSize.h + this.lineSpacing @@ -275,17 +280,18 @@ class CandidateBox { } ReleaseFonts() { - DeleteFont(this.mainFontObj) - DeleteFont(this.labFontObj) - DeleteFont(this.commentFontObj) - - DeleteFont(oFnt) { - if (oFnt.hFont) - Gdip_DeleteFont(oFnt.hFont), oFnt.hFont := 0 - if (oFnt.hFamily) - Gdip_DeleteFontFamily(oFnt.hFamily), oFnt.hFamily := 0 - if (oFnt.hFormat) - Gdip_DeleteStringFormat(oFnt.hFormat), oFnt.hFormat := 0 + DeleteFont(this.mainFontHs) + DeleteFont(this.labFontHs) + DeleteFont(this.commentFontHs) + DeleteFont(this.fallbackFontHs) + + DeleteFont(fntHs) { + if (fntHs.hFont) + Gdip_DeleteFont(fntHs.hFont), fntHs.hFont := 0 + if (fntHs.hFamily) + Gdip_DeleteFontFamily(fntHs.hFamily), fntHs.hFamily := 0 + if (fntHs.hFormat) + Gdip_DeleteStringFormat(fntHs.hFormat), fntHs.hFormat := 0 } } @@ -315,23 +321,18 @@ class CandidateBox { } } - MeasureString(pGraphics, text, hFont, hFormat, &RectF) { + MeasureString(pGraphics, text, fontHs, &RectF) { if !text return { w: 0, h: 0 } rc := Buffer(16) - ; !Notice, this way gets incorrect dim in test - ; dim := Gdip_MeasureString(pGraphics, text, hFont, hFormat, &rc) - ; rect := StrSplit(dim, "|") - ; return { w: Round(rect[3]), h: Round(rect[4]) } - DllCall("gdiplus\GdipMeasureString", "Ptr", pGraphics, "WStr", text, "Int", -1, - "Ptr", hFont, + "Ptr", fontHs.hFont, "Ptr", RectF.Ptr, - "Ptr", hFormat, + "Ptr", fontHs.hFormat, "Ptr", rc.Ptr, "UInt*", 0, "UInt*", 0, @@ -340,13 +341,80 @@ class CandidateBox { return { w: NumGet(rc.Ptr, 8, "Float"), h: NumGet(rc.Ptr, 12, "Float") } } - DrawText(pGraphics, fontObj, text, textRect, color) { + ; slicedInfo like-> [{x:10,y:10,w:80,h:30,isUSymbol:0,slicedStrs:"satisfy"}, + ; {x:90,y:10,w:80,h:30,isUSymbol:1,slicedStrs:"😎😎😎"}, ...] + MakeSlicedStrsInfo(pGraphics, xOffset, text, fontHs, &textBoxSize, &RC) { + if !text { + textBoxSize := { w: 0, h: 0 } + return [] + } + + ; slice text to pieces by emojis and special Symbols + pattern := "([\x{1F000}-\x{1FAFF}\x{2300}-\x{23FF}\x{2600}-\x{27BF}\x{1D400}-\x{1D7FF}\x{FE0F}]+)" + pieces := [] + pos := 1 + while pos := RegExMatch(text, pattern, &m, pos) { + if (pos > 1) { + normal := SubStr(text, 1, pos - 1) + if (normal != "") + pieces.Push({ isUSymbol: 0, slicedStrs: normal }) + } + ; emojis and unicode Symbols + pieces.Push({ isUSymbol: 1, slicedStrs: m[1] }) + + text := SubStr(text, pos + StrLen(m[1])) + pos := 1 + } + if (text != "") + pieces.Push({ isUSymbol: 0, slicedStrs: text }) + + + ; build sliced strings info + strsInfoArr := [] + totalRowW := 0, maxRowH := 0 + curX := xOffset + rowY := this.lineSpacing + this.borderWidth + for i, p in pieces { + sym := p.isUSymbol + strs := p.slicedStrs + size := this.MeasureString(pGraphics, strs, sym ? this.fallbackFontHs : fontHs, &RC) + + totalRowW += size.w + if (size.h > maxRowH) + maxRowH := size.h + + strsInfoArr.Push({ + x: curX, + y: rowY, + w: size.w, + h: size.h, + isUSymbol: sym, + slicedStrs: strs + }) + + curX += size.w + } + + textBoxSize := { w: totalRowW, h: maxRowH } + return strsInfoArr + } + + DrawText(pGraphics, fontHs, text, textRect, color) { this.pBrush := Gdip_BrushCreateSolid(color) CreateRectF(&RC, textRect.x, textRect.y, textRect.w, textRect.h) - Gdip_DrawString(pGraphics, text, fontObj.hFont, fontObj.hFormat, this.pBrush, &RC) + Gdip_DrawString(pGraphics, text, fontHs.hFont, fontHs.hFormat, this.pBrush, &RC) Gdip_DeleteBrush(this.pBrush) } + DrawSlicedTexts(pGraphics, fontHs, rowBaseY, rowMaxH, slicedStrsInfo, color) { + loop slicedStrsInfo.Length { + info := slicedStrsInfo[A_Index] + info.y := rowBaseY + info.h := rowMaxH + this.DrawText(this.pGraphics, info.isUSymbol ? this.fallbackFontHs : fontHs, info.slicedStrs, info, color) + } + } + FillRoundedRect(pGraphics, pBrush, x, y, w, h, r) { if (r <= 0) { Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h) From 12564128d09362d9a319e795111c4142403001ae Mon Sep 17 00:00:00 2001 From: Tim <67952813+rawbx@users.noreply.github.com> Date: Sun, 7 Sep 2025 06:20:13 +0800 Subject: [PATCH 33/38] fix some unicode symbol display error --- Lib/RabbitCandidateBox.ahk | 311 +++++++++++++++++++++++++------------ 1 file changed, 209 insertions(+), 102 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index 2aba304..c40e4ca 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -34,11 +34,6 @@ class CandidateBox { hBitmap := 0 oBitmap := 0 pGraphics := 0 - mainFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } - labFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } - commentFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } - fallbackFontHs := { hFamily: 0, hFont: 0, hFormat: 0 } - static isHidden := 1 __New() { @@ -51,7 +46,7 @@ class CandidateBox { } ; +E0x8080088: WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST this.gui := Gui("-Caption +E0x8080088 +LastFound -DPIScale +AlwaysOnTop", "CandidateBox") - this.dpiSacle := GUIUtilities.GetMonitorDpiScale() + this.dpiScale := GUIUtilities.GetMonitorDpiScale() this.UpdateUIStyle() } @@ -71,8 +66,9 @@ class CandidateBox { this.mainFontHs := this.CreateFontObj(UIStyle.font_face, UIStyle.font_point) this.labFontHs := this.CreateFontObj(UIStyle.label_font_face, UIStyle.label_font_point) this.commentFontHs := this.CreateFontObj(UIStyle.comment_font_face, UIStyle.comment_font_point) - this.fallbackFontHs := this.CreateFontObj("Segoe UI Emoji", UIStyle.font_point) - ; this.symbolFontHs := this.CreateFontObj("Segoe UI Symbol", UIStyle.font_point) + ; fallback fonts + this.emojiFontHs := this.CreateFontObj("Segoe UI Emoji", UIStyle.font_point) + this.symbolFontHs := this.CreateFontObj("Segoe UI Symbol", UIStyle.font_point) ; preedite style this.textColor := UIStyle.text_color @@ -95,7 +91,7 @@ class CandidateBox { CreateFontObj(name, size) { local em2pt := 96.0 / 72.0 hFamily := Gdip_FontFamilyCreate(name) - hFont := Gdip_FontCreate(hFamily, size * em2pt * this.dpiSacle, regular := 0) + hFont := Gdip_FontCreate(hFamily, size * em2pt * this.dpiScale, regular := 0) hFormat := Gdip_StringFormatCreate(0x0001000 | 0x0004000) ; nowrap and noclip Gdip_SetStringFormatAlign(hFormat, left := 0) ; left:0, center:1, right:2 ; vertical align(top:0, center:1, bottom:2) @@ -103,7 +99,7 @@ class CandidateBox { return { hFamily: hFamily, hFont: hFont, hFormat: hFormat } } - Build(context, &calcW, &calcH) { + Build(context, &winW, &winH) { ; build text layout local menu := context.menu local cands := menu.candidates this.num_candidates := menu.num_candidates @@ -113,26 +109,35 @@ class CandidateBox { this.prdSelTxt := pre_selected this.prdHlSelTxt := selected this.prdHlUnselTxt := post_selected - this.labelsSliceInfo := [] - this.candsSliceInfo := [] - this.commentsSliceInfo := [] local hDC := GetDC(this.gui.Hwnd) local pGraphics := Gdip_GraphicsFromHDC(hDC) CreateRectF(&RC, 0, 0, 0, 0) - ; Measure preedit texts - baseXOffset := this.padding + this.borderWidth - ; prdSelTxt may contains unicode symbols - this.prdSelTxtSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset, this.prdSelTxt, this.mainFontHs, &textBoxSize, &RC) - this.prdSelSize := textBoxSize - this.prdHlSelSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.mainFontHs, &RC) - this.prdHlUnselSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.mainFontHs, &RC) - - ; Measure candidate texts - this.candRowSizes := [] - maxRowWidth := this.prdSelSize.w + this.padding + this.prdHlSelSize.w + this.prdHlUnselSize.w - totalHeight := Max(this.prdSelSize.h, this.prdHlSelSize.h, this.prdHlUnselSize.h) + this.lineSpacing + ; Build preedit layout + baseX := this.borderWidth + this.padding + baseY := this.borderWidth + this.lineSpacing + prdSelTxtSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, baseX, baseY, this.prdSelTxt, this.mainFontHs, &prdSelTxtBoxSize, &RC) + prdHlSelTxtSize := this.MeasureString(pGraphics, this.prdHlSelTxt, this.mainFontHs, &RC) + prdHlSelTxtX := baseX + prdSelTxtBoxSize.w + this.padding + prdHlUnSelTxtSize := this.MeasureString(pGraphics, this.prdHlUnselTxt, this.mainFontHs, &RC) + prdHlUnSelTxtX := prdHlSelTxtX + prdHlSelTxtSize.w + this.preeditLayout := { + selBox: { size: prdSelTxtBoxSize, sliced: prdSelTxtSlicedInfo }, ; prdSelTxt may contains unicode symbols + hlSelBox: { size: prdHlSelTxtSize, rect: { x: prdHlSelTxtX, y: baseY, + w: prdHlSelTxtSize.w, h: prdHlSelTxtSize.h } }, + hlUnSelBox: { size: prdHlUnSelTxtSize, rect: { x: prdHlUnSelTxtX, y: baseY, w: prdHlUnSelTxtSize.w, h: prdHlUnSelTxtSize.h } }, + left: baseX, + top: baseY, + width: prdSelTxtBoxSize.w + this.padding + prdHlSelTxtSize.w + prdHlUnSelTxtSize.w, + height: Max(prdSelTxtBoxSize.h, prdHlSelTxtSize.h, prdHlUnSelTxtSize.h) + } + this.maxRowWidth := this.preeditLayout.width + + ; Build candidates layout + totalRowsHeight := this.preeditLayout.height + this.lineSpacing + baseY := baseY + totalRowsHeight + this.candidatesLayout := { labels: [], cands: [], comments: [], rows: [] } has_label := !!context.select_labels[0] select_keys := menu.select_keys @@ -146,48 +151,52 @@ class CandidateBox { else if A_Index <= num_select_keys labelText := SubStr(select_keys, A_Index, 1) labelText := Format(UIStyle.label_format, labelText) - labelSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset, labelText, this.labFontHs, &labelBoxSize, &RC) - this.labelsSliceInfo.Push({ w: labelBoxSize.w, h: labelBoxSize.h, sliceInfo: labelSlicedInfo }) + labelSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, baseX, baseY, labelText, this.labFontHs, &labelBoxSize, &RC) + this.candidatesLayout.labels.Push({ size: labelBoxSize, sliced: labelSlicedInfo }) candText := cands[A_Index].text - candSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset + labelBoxSize.w, candText, this.mainFontHs, &candBoxSize, &RC) - this.candsSliceInfo.Push({ w: candBoxSize.w, h: candBoxSize.h, sliceInfo: candSlicedInfo }) + candSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, baseX + labelBoxSize.w, baseY, candText, this.mainFontHs, &candBoxSize, &RC) + this.candidatesLayout.cands.Push({ size: candBoxSize, sliced: candSlicedInfo }) commentText := cands[A_Index].comment - commentSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, xOffset := baseXOffset + labelBoxSize.w + candBoxSize.w, commentText, this.commentFontHs, &commentBoxSize, &RC) - this.commentsSliceInfo.Push({ w: commentBoxSize.w, h: commentBoxSize.h, sliceInfo: commentSlicedInfo }) + commentSlicedInfo := this.MakeSlicedStrsInfo(pGraphics, baseX + labelBoxSize.w + candBoxSize.w, baseY, commentText, this.commentFontHs, &commentBoxSize, &RC) + this.candidatesLayout.comments.Push({ size: commentBoxSize, sliced: commentSlicedInfo }) - rowSize := { + rowRect := { + x: baseX, y: baseY, w: labelBoxSize.w + candBoxSize.w + (commentText ? this.padding * 2 + commentBoxSize.w : 0), h: Max(labelBoxSize.h, candBoxSize.h, commentBoxSize.h) } - this.candRowSizes.Push(rowSize) - if (rowSize.w > maxRowWidth) { - maxRowWidth := rowSize.w + this.candidatesLayout.rows.Push(rowRect) + if (rowRect.w > this.maxRowWidth) { + this.maxRowWidth := rowRect.w } - totalHeight += rowSize.h + this.lineSpacing + increment := rowRect.h + this.lineSpacing + baseY += increment, totalRowsHeight += increment } - totalHeight -= this.lineSpacing ; remove extra line spacing + totalRowsHeight -= this.lineSpacing ; remove extra line spacing Gdip_DeleteGraphics(pGraphics) ReleaseDC(hDC, this.gui.Hwnd) this.commentOffset := 0 - this.boxWidth := Ceil(maxRowWidth) + this.padding * 2 + this.borderWidth * 2 + this.boxWidth := Ceil(this.maxRowWidth) + (this.borderWidth + this.padding) * 2 if this.boxWidth < UIStyle.min_width { this.commentOffset := UIStyle.min_width - this.boxWidth this.boxWidth := UIStyle.min_width } - this.boxHeight := Ceil(totalHeight) + this.padding * 2 + this.borderWidth * 2 - Round(this.lineSpacing / 2) - calcW := this.boxWidth - calcH := this.boxHeight + this.boxHeight := Ceil(totalRowsHeight) + (this.borderWidth + this.padding) * 2 + winW := this.boxWidth + winH := this.boxHeight ; get better spacing to align comments loop this.num_candidates { - commentW := this.commentsSliceInfo[A_Index].w + labelW := this.candidatesLayout.labels[A_Index].size.w + candW := this.candidatesLayout.cands[A_Index].size.w + commentW := this.candidatesLayout.comments[A_Index].size.w if commentW > 0 { - alignCommentGap := maxRowWidth - this.labelsSliceInfo[A_Index].w - this.candsSliceInfo[A_Index].w - commentW - for _, info in this.commentsSliceInfo[A_Index].sliceInfo + alignCommentGap := this.maxRowWidth - labelW - candW - commentW + for _, info in this.candidatesLayout.comments[A_Index].sliced info.x := info.x + alignCommentGap + this.commentOffset } } @@ -216,19 +225,16 @@ class CandidateBox { ; Draw background pBrushBg := Gdip_BrushCreateSolid(this.backgroundColor) bgX := this.borderWidth, bgY := this.borderWidth - bgW := this.boxWidth - this.borderWidth * 2 - bgH := this.boxHeight - this.borderWidth * 2 + bgW := this.boxWidth - this.borderWidth * 2, bgH := this.boxHeight - this.borderWidth * 2 bgCornerRadius := this.boxCornerR > this.borderWidth ? this.boxCornerR - this.borderWidth : 0 this.FillRoundedRect(this.pGraphics, pBrushBg, bgX, bgY, bgW, bgH, bgCornerRadius) Gdip_DeleteBrush(pBrushBg) ; Draw preedit rectShrink := 2 - currentY := this.lineSpacing + this.borderWidth - prdSelTxtRc := { x: this.padding + this.borderWidth, y: currentY, w: this.prdSelSize.w, h: this.prdSelSize.h } - prdHlSelTxtRc := { x: prdSelTxtRc.x + prdSelTxtRc.w + this.padding, y: currentY, w: this.prdHlSelSize.w, h: this.prdHlSelSize.h } - prdHlUnselTxtRc := { x: prdHlSelTxtRc.x + prdHlSelTxtRc.w, y: currentY, w: this.prdHlUnselSize.w, h: this.prdHlUnselSize.h } - this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, currentY, this.prdSelSize.h, this.prdSelTxtSlicedInfo, this.textColor) + this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, this.preeditLayout.height, this.preeditLayout.selBox.sliced, this.textColor) + prdHlSelTxtRc := this.preeditLayout.hlSelBox.rect + prdHlUnselTxtRc := this.preeditLayout.hlUnSelBox.rect if this.prdHlSelTxt { pBrsh_hlSelBg := Gdip_BrushCreateSolid(this.hlBgColor) Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlSelBg, prdHlSelTxtRc.x - rectShrink, prdHlSelTxtRc.y, prdHlSelTxtRc.w, prdHlSelTxtRc.h - rectShrink, this.hlCornerR) @@ -236,11 +242,10 @@ class CandidateBox { } this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) - currentY += Max(this.prdSelSize.h, this.prdHlSelSize.h, this.prdHlUnselSize.h) + this.lineSpacing ; Draw candidates Loop this.num_candidates { - rowSize := this.candRowSizes[A_Index] + rowRect := this.candidatesLayout.rows[A_Index] labelFg := this.labelColor candFg := this.candTxtColor commentFg := this.commentTxtColor @@ -249,26 +254,20 @@ class CandidateBox { candFg := this.hlCandTxtColor commentFg := this.hlCommentTxtColor pBrsh_hlCandBg := Gdip_BrushCreateSolid(this.hlCandBgColor) - highlightX := this.borderWidth + this.padding / 2 - highlightY := currentY - this.lineSpacing / 2 - highlightW := this.boxWidth - this.borderWidth * 2 - this.padding - highlightH := rowSize.h + this.lineSpacing - rectShrink - Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, highlightX, highlightY, highlightW, highlightH, this.hlCornerR) + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, rowRect.x, rowRect.y, this.maxRowWidth, rowRect.h, this.hlCornerR) Gdip_DeleteBrush(pBrsh_hlCandBg) } - this.DrawSlicedTexts(this.pGraphics, this.labFontHs, currentY, rowSize.h, this.labelsSliceInfo[A_Index].sliceInfo, labelFg) - this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, currentY, rowSize.h, this.candsSliceInfo[A_Index].sliceInfo, candFg) + this.DrawSlicedTexts(this.pGraphics, this.labFontHs, rowRect.h, this.candidatesLayout.labels[A_Index].sliced, labelFg) + this.DrawSlicedTexts(this.pGraphics, this.mainFontHs, rowRect.h, this.candidatesLayout.cands[A_Index].sliced, candFg) - if this.commentsSliceInfo[A_Index].w > 0 { - this.DrawSlicedTexts(this.pGraphics, this.commentFontHs, currentY, rowSize.h, this.commentsSliceInfo[A_Index].sliceInfo, commentFg) + commentLayout := this.candidatesLayout.comments[A_Index] + if commentLayout.size.w > 0 { + this.DrawSlicedTexts(this.pGraphics, this.commentFontHs, rowRect.h, commentLayout.sliced, commentFg) } - - currentY += rowSize.h + this.lineSpacing } UpdateLayeredWindow(this.gui.Hwnd, this.hDC, x, y, this.boxWidth, this.boxHeight) - this.ReleaseDrawingSurface() } @@ -283,7 +282,8 @@ class CandidateBox { DeleteFont(this.mainFontHs) DeleteFont(this.labFontHs) DeleteFont(this.commentFontHs) - DeleteFont(this.fallbackFontHs) + DeleteFont(this.emojiFontHs) + DeleteFont(this.symbolFontHs) DeleteFont(fntHs) { if (fntHs.hFont) @@ -341,62 +341,146 @@ class CandidateBox { return { w: NumGet(rc.Ptr, 8, "Float"), h: NumGet(rc.Ptr, 12, "Float") } } - ; slicedInfo like-> [{x:10,y:10,w:80,h:30,isUSymbol:0,slicedStrs:"satisfy"}, - ; {x:90,y:10,w:80,h:30,isUSymbol:1,slicedStrs:"😎😎😎"}, ...] - MakeSlicedStrsInfo(pGraphics, xOffset, text, fontHs, &textBoxSize, &RC) { + MakeSymbolsInfo(pGraphics, xOffset, yOffset, sType, cpArr, &symsBoxSize, &RectF) { + symsInfoArr := [] + totalSymRowW := 0, maxSymRowH := 0 + curX := xOffset, rowY := yOffset + isEmoji := sType == 2 + loop cpArr.Length { + curCP := cpArr[A_Index] + symSize := this.MeasureString(pGraphics, curCP, isEmoji ? this.emojiFontHs : this.symbolFontHs, &RectF) + + totalSymRowW += symSize.w + if (symSize.h > maxSymRowH) + maxSymRowH := symSize.h + + symsInfoArr.Push({ + x: curX, + y: rowY, + w: symSize.w, + h: symSize.h, + isEmoji: isEmoji + }) + + curX += symSize.w + } + + symsBoxSize := { w: totalSymRowW, h: maxSymRowH } + return symsInfoArr + } + + MakeSlicedStrsInfo(pGraphics, xOffset, yOffset, text, fontHs, &textBoxSize, &RC) { if !text { textBoxSize := { w: 0, h: 0 } return [] } ; slice text to pieces by emojis and special Symbols - pattern := "([\x{1F000}-\x{1FAFF}\x{2300}-\x{23FF}\x{2600}-\x{27BF}\x{1D400}-\x{1D7FF}\x{FE0F}]+)" + pattern := "([\x{1F000}-\x{1FAFF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1D400}-\x{1D7FF}\x{200D}\x{FE0F}\x{FE0E}]+)" + pieces := [] pos := 1 - while pos := RegExMatch(text, pattern, &m, pos) { - if (pos > 1) { - normal := SubStr(text, 1, pos - 1) + while (RegExMatch(text, pattern, &m, pos)) { + start := m.Pos(1), length := m.Len(1) + ; group normal text + if (start > pos) { + normal := SubStr(text, pos, start - pos) if (normal != "") - pieces.Push({ isUSymbol: 0, slicedStrs: normal }) + pieces.Push({ sType: 0, value: normal }) + } + ; group emoji/unicode symbols with codePoints + cpArr := getCodePoints(m[1]) + if RegExMatch(m[1], "^[\x{1F300}-\x{1FAFF}\x{1F900}-\x{1F9FF}\x{200D}\x{FE0F}\x{FE0E}]$") { + pieces.Push({ sType: 2, value: cpArr }) + } else { + pieces.Push({ sType: 1, value: cpArr }) } - ; emojis and unicode Symbols - pieces.Push({ isUSymbol: 1, slicedStrs: m[1] }) - text := SubStr(text, pos + StrLen(m[1])) - pos := 1 + pos := start + length + } + ; group remaining normal text + if (pos <= StrLen(text)) { + rest := SubStr(text, pos) + if (rest != "") + pieces.Push({ sType: 0, value: rest }) } - if (text != "") - pieces.Push({ isUSymbol: 0, slicedStrs: text }) - ; build sliced strings info - strsInfoArr := [] + slicedInfo := [] totalRowW := 0, maxRowH := 0 - curX := xOffset - rowY := this.lineSpacing + this.borderWidth + curX := xOffset, curY := yOffset for i, p in pieces { - sym := p.isUSymbol - strs := p.slicedStrs - size := this.MeasureString(pGraphics, strs, sym ? this.fallbackFontHs : fontHs, &RC) - - totalRowW += size.w - if (size.h > maxRowH) - maxRowH := size.h + sType := p.sType + content := p.value + slicedSize := { w: 0, h: 0 } + slicedSymsInfo := [] + if sType { + slicedSymsInfo := this.MakeSymbolsInfo(pGraphics, curX, curY, sType, content, &symsBoxSize, &RC) + slicedSize := symsBoxSize + } else { + slicedSize := this.MeasureString(pGraphics, content, fontHs, &RC) + } + totalRowW += slicedSize.w + if (slicedSize.h > maxRowH) + maxRowH := slicedSize.h - strsInfoArr.Push({ + slicedInfo.Push({ x: curX, - y: rowY, - w: size.w, - h: size.h, - isUSymbol: sym, - slicedStrs: strs + y: curY, + w: slicedSize.w, + h: slicedSize.h, + sType: sType, + slicedSymsInfo: slicedSymsInfo, + value: content }) - curX += size.w + curX += slicedSize.w } textBoxSize := { w: totalRowW, h: maxRowH } - return strsInfoArr + return slicedInfo + + getCodePoints(str) { + arr := [] + i := 1, len := StrLen(str) + + ; only keep first codepoint in pair + if (i <= len) { + cp := Ord(SubStr(str, i, 1)) + if (cp >= 0xD800 && cp <= 0xDBFF && i < len) { + low := Ord(SubStr(str, i + 1, 1)) + if (low >= 0xDC00 && low <= 0xDFFF) { + arr.Push(Chr(0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00))) + i += 2 + } else { + arr.Push(Chr(cp)) + i++ + } + } else { + arr.Push(Chr(cp)) + i++ + } + } + + ; skip ZWJ, VS15/VS16 + while (i <= len) { + cp := Ord(SubStr(str, i, 1)) + if (cp = 0x200D or cp = 0xFE0F or cp = 0xFE0E) { + i++ + if (i <= len) { + cp2 := Ord(SubStr(str, i, 1)) + if (cp2 >= 0xD800 && cp2 <= 0xDBFF && i < len) + i += 2 + else + i++ + } + } else { + break + } + } + + return arr + } } DrawText(pGraphics, fontHs, text, textRect, color) { @@ -406,12 +490,35 @@ class CandidateBox { Gdip_DeleteBrush(this.pBrush) } - DrawSlicedTexts(pGraphics, fontHs, rowBaseY, rowMaxH, slicedStrsInfo, color) { + DrawSlicedTexts(pGraphics, fontHs, rowMaxH, slicedStrsInfo, color) { + this.pBrush := Gdip_BrushCreateSolid(color) loop slicedStrsInfo.Length { info := slicedStrsInfo[A_Index] - info.y := rowBaseY info.h := rowMaxH - this.DrawText(this.pGraphics, info.isUSymbol ? this.fallbackFontHs : fontHs, info.slicedStrs, info, color) + if info.sType + drawEmojiAndSymbols(this.pGraphics, this.pBrush, info) + else + drawNormalText(this.pGraphics, this.pBrush, fontHs, info) + } + Gdip_DeleteBrush(this.pBrush) + + drawNormalText(pGraphics, pBrush, fontHs, textInfo) { + CreateRectF(&RC, textInfo.x, textInfo.y, textInfo.w, textInfo.h) + Gdip_DrawString(pGraphics, textInfo.value, fontHs.hFont, fontHs.hFormat, pBrush, &RC) + } + + drawEmojiAndSymbols(pGraphics, pBrush, textInfo) { + cpArr := textInfo.value + uSymsMaxH := textInfo.h + uSymsInfo := textInfo.slicedSymsInfo + loop cpArr.Length { + CreateRectF(&RC, uSymsInfo[A_Index].x, uSymsInfo[A_Index].y, uSymsInfo[A_Index].w, uSymsMaxH) + if uSymsInfo[A_Index].isEmoji + Gdip_DrawString(pGraphics, cpArr[A_Index], this.emojiFontHs.hFont, this.emojiFontHs.hFormat, pBrush, &RC) + else { ; is unicode Symbols + Gdip_DrawString(pGraphics, cpArr[A_Index], this.symbolFontHs.hFont, this.symbolFontHs.hFormat, pBrush, &RC) + } + } } } From 2467abd1cee19126a1fec25e6583ea7180ff21f7 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 9 Sep 2025 15:05:24 +0800 Subject: [PATCH 34/38] fix: highlighted candidate background size --- Lib/RabbitCandidateBox.ahk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index c40e4ca..b2fcb22 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -243,6 +243,7 @@ class CandidateBox { this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlSelTxt, prdHlSelTxtRc, this.hlTxtColor) this.DrawText(this.pGraphics, this.mainFontHs, this.prdHlUnselTxt, prdHlUnselTxtRc, this.textColor) + hiliteW := this.boxWidth - this.borderWidth * 2 - this.padding * 2 ; Draw candidates Loop this.num_candidates { rowRect := this.candidatesLayout.rows[A_Index] @@ -254,7 +255,7 @@ class CandidateBox { candFg := this.hlCandTxtColor commentFg := this.hlCommentTxtColor pBrsh_hlCandBg := Gdip_BrushCreateSolid(this.hlCandBgColor) - Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, rowRect.x, rowRect.y, this.maxRowWidth, rowRect.h, this.hlCornerR) + Gdip_FillRoundedRectangle(this.pGraphics, pBrsh_hlCandBg, rowRect.x, rowRect.y, hiliteW, rowRect.h, this.hlCornerR) Gdip_DeleteBrush(pBrsh_hlCandBg) } From cb25201e4cf3466804384d38931fa18609c24f28 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 13 Sep 2025 15:27:00 +0800 Subject: [PATCH 35/38] chore: add stdout log --- Lib/RabbitCommon.ahk | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Lib/RabbitCommon.ahk b/Lib/RabbitCommon.ahk index 67afc9e..e0f1925 100644 --- a/Lib/RabbitCommon.ahk +++ b/Lib/RabbitCommon.ahk @@ -105,8 +105,10 @@ CreateTraits() { } RabbitUserDataPath() { - if FileExist(A_ScriptDir . "\.portable") + if FileExist(A_ScriptDir . "\.portable") { + RabbitDebug("run in portable mode.", Format("RabbitCommon.ahk:{}", A_LineNumber), 1) return A_ScriptDir . "\Rime" + } try { local dir := RegRead("HKEY_CURRENT_USER\Software\Rime\Rabbit", "RimeUserDir") } @@ -173,3 +175,33 @@ CleanOldLogs() { } } } + +RabbitLog(text) { + try { + FileAppend(text, "*", "UTF-8") + } +} +RabbitLogLimit(text, label, limit := 1) { + static labels := Map() + if !labels.Has(label) + labels[label] := 0 + if limit < 0 || labels[label] < limit { + RabbitLog(text) + labels[label] := labels[label] + 1 + } +} +RabbitError(text, location, limit := -1) { + msg := Format("E{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + RabbitLogLimit(msg, location, limit) +} +RabbitInfo(text, location, limit := -1) { + msg := Format("I{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + RabbitLogLimit(msg, location, limit) +} +RabbitDebug(text, location, limit := -1) { + global RABBIT_VERSION + if !SubStr(RABBIT_VERSION, 1, 3) = "dev" + return + msg := Format("D{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + RabbitLogLimit(msg, location, limit) +} From 7912d9ad64b6328b8dc986dd6436efade040dcd5 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 13 Sep 2025 15:28:23 +0800 Subject: [PATCH 36/38] fix: symbols and emojis only display first code point --- Lib/RabbitCandidateBox.ahk | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/Lib/RabbitCandidateBox.ahk b/Lib/RabbitCandidateBox.ahk index b2fcb22..5cb8af3 100644 --- a/Lib/RabbitCandidateBox.ahk +++ b/Lib/RabbitCandidateBox.ahk @@ -445,10 +445,12 @@ class CandidateBox { arr := [] i := 1, len := StrLen(str) - ; only keep first codepoint in pair - if (i <= len) { + ; skip ZWJ, VS15/VS16 as GDI does not support color fonts + while (i <= len) { cp := Ord(SubStr(str, i, 1)) - if (cp >= 0xD800 && cp <= 0xDBFF && i < len) { + if (cp = 0x200D or cp = 0xFE0F or cp = 0xFE0E) { + i++ + } else if cp >= 0xD800 && cp <= 0xDBFF && i < len { low := Ord(SubStr(str, i + 1, 1)) if (low >= 0xDC00 && low <= 0xDFFF) { arr.Push(Chr(0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00))) @@ -463,23 +465,6 @@ class CandidateBox { } } - ; skip ZWJ, VS15/VS16 - while (i <= len) { - cp := Ord(SubStr(str, i, 1)) - if (cp = 0x200D or cp = 0xFE0F or cp = 0xFE0E) { - i++ - if (i <= len) { - cp2 := Ord(SubStr(str, i, 1)) - if (cp2 >= 0xD800 && cp2 <= 0xDBFF && i < len) - i += 2 - else - i++ - } - } else { - break - } - } - return arr } } From 708703117402333a86fb4fcd24a656b8e5e93436 Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Sat, 13 Sep 2025 15:30:56 +0800 Subject: [PATCH 37/38] fix: date format in log --- Lib/RabbitCommon.ahk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/RabbitCommon.ahk b/Lib/RabbitCommon.ahk index e0f1925..840a287 100644 --- a/Lib/RabbitCommon.ahk +++ b/Lib/RabbitCommon.ahk @@ -191,17 +191,17 @@ RabbitLogLimit(text, label, limit := 1) { } } RabbitError(text, location, limit := -1) { - msg := Format("E{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + msg := Format("E{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMdd HH:mm:ss "), ProcessExist(), location, text) RabbitLogLimit(msg, location, limit) } RabbitInfo(text, location, limit := -1) { - msg := Format("I{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + msg := Format("I{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMdd HH:mm:ss "), ProcessExist(), location, text) RabbitLogLimit(msg, location, limit) } RabbitDebug(text, location, limit := -1) { global RABBIT_VERSION if !SubStr(RABBIT_VERSION, 1, 3) = "dev" return - msg := Format("D{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMDD HH:mm:ss "), ProcessExist(), location, text) + msg := Format("D{} {:5} {}] {}`r`n", FormatTime(, "yyyyMMdd HH:mm:ss "), ProcessExist(), location, text) RabbitLogLimit(msg, location, limit) } From 41cc35c557d850e15ebf541b1302fb6e1bb326dd Mon Sep 17 00:00:00 2001 From: Xuesong Peng Date: Tue, 16 Sep 2025 17:38:11 +0800 Subject: [PATCH 38/38] chore: update readme --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8fa0bfc..02b39d0 100644 --- a/README.md +++ b/README.md @@ -6,28 +6,29 @@ [![Build Status](https://github.com/rimeinn/rabbit/actions/workflows/ci.yaml/badge.svg)](https://github.com/rimeinn/rabbit/actions/workflows/ci.yaml) [![Telegram Group Chat](https://telegram-badge.vercel.app/api/telegram-badge?channelId=@rime_rabbit)](https://t.me/rime_rabbit) [![License](https://img.shields.io/github/license/rimeinn/rabbit)](LICENSE) -![GitHub Repo stars](https://img.shields.io/github/stars/rimeinn/rabbit?style=flat) +[![GitHub Repo stars](https://img.shields.io/github/stars/rimeinn/rabbit?style=flat)](https://github.com/rimeinn/rabbit/stargazers) ## 下载体验 > [!NOTE] > 发现程序漏洞请在 [Issues](https://github.com/rimeinn/rabbit/issues/new/choose) 反馈。使用问题可以在 [Discussions](https://github.com/rimeinn/rabbit/discussions) 讨论,或者加入 [Telegram 群聊](https://t.me/rime_rabbit)。 -### Release 版 +### 通过发布页面下载 -发行版会在 [Release 页面](https://github.com/rimeinn/rabbit/releases) 的 Assets 中,下载最新的 `rabbit-v<版本号>.zip`,解压到一个新建文件夹,运行 `Rabbit.exe` 即可。 +正式发行版会在 [Release 页面](https://github.com/rimeinn/rabbit/releases) 的 Assets 中,下载最新的 `rabbit-v<版本号>.zip`,解压到一个新建文件夹,运行 `Rabbit.exe` 即可。 -#### 通过 [scoop](https://scoop.sh/) 安装 +每夜构建版可在 [`latest`](https://github.com/rimeinn/rabbit/releases/tag/latest) 页面下载。 + +### 通过 [scoop](https://scoop.sh/) 安装 ```PowerShell scoop bucket add siku https://github.com/amorphobia/siku +# 正式发行版 scoop install siku/rabbit +# 每夜构建版 +scoop install siku/rabbit-nightly ``` -### 每夜构建版本 (Nightly) - -每夜构建版本可在 [`latest` 标签](https://github.com/rimeinn/rabbit/releases/tag/latest)页面下载。 - ## 脚本编译 本仓库提供*源码形式的玉兔毫脚本*以及*仅修改主图标的 AutoHotkey 可执行文件*,用户可根据需要自行编译为可执行文件以及压缩。编译方式可参照 AutoHotkey 的[官方文档](https://www.autohotkey.com/docs/v2/Scripts.htm#ahk2exe)。 @@ -36,6 +37,9 @@ scoop install siku/rabbit ## 目录结构 +
+点击展开 + > [!NOTE] > 以下描述的*可删除*、*编译后可删除*指的是删除后不影响使用,若要再次分发脚本或编译后的可执行文件,需遵守 [GPL-3.0 开源许可](LICENSE)。 @@ -57,6 +61,8 @@ rabbit/ ├─ rime-install.bat 东风破批处理脚本,删除后无法从设定中调用东风破 ``` +
+ ## 使用的开源项目 - [librime](https://github.com/rime/librime)