Skip to content

Commit 612da2d

Browse files
committed
Add rich clipboard (CF_HTML build/parse/get/set)
1 parent a3ed5f8 commit 612da2d

15 files changed

Lines changed: 402 additions & 1 deletion

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 丰富剪贴板(HTML / CF_HTML)
4+
5+
*格式化*的 HTML 复制粘贴到 Word / Outlook。完整参考:[`docs/source/Zh/doc/new_features/v144_features_doc.rst`](../docs/source/Zh/doc/new_features/v144_features_doc.rst)
6+
7+
- **`build_cf_html` / `parse_cf_html` / `set_clipboard_html` / `get_clipboard_html`**(`AC_set_clipboard_html``AC_get_clipboard_html`):基础剪贴板只处理纯文字 + 图像——富文字粘贴需要 `CF_HTML`,其字节偏移标头(`StartHTML`/`EndHTML`/`StartFragment`/`EndFragment`)极易出错。`build_cf_html`/`parse_cf_html` 以纯 Python 计算与还原它(往返测试、多字节 UTF-8 正确);`set/get_clipboard_html` 将其包装于 Win32 剪贴板(含纯文字后备)。字节偏移运算可无头测试;只有 I/O 为 Windows。
8+
39
## 本次更新 (2026-06-23) — 可串接 / 可过滤的候选定位器
410

511
用链式调用细化已定位的元素:`.within(panel).filter(has_text="Delete").nth(1)`。完整参考:[`docs/source/Zh/doc/new_features/v143_features_doc.rst`](../docs/source/Zh/doc/new_features/v143_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 豐富剪貼簿(HTML / CF_HTML)
4+
5+
*格式化*的 HTML 複製貼上到 Word / Outlook。完整參考:[`docs/source/Zh/doc/new_features/v144_features_doc.rst`](../docs/source/Zh/doc/new_features/v144_features_doc.rst)
6+
7+
- **`build_cf_html` / `parse_cf_html` / `set_clipboard_html` / `get_clipboard_html`**(`AC_set_clipboard_html``AC_get_clipboard_html`):基礎剪貼簿只處理純文字 + 影像——富文字貼上需要 `CF_HTML`,其位元組偏移標頭(`StartHTML`/`EndHTML`/`StartFragment`/`EndFragment`)極易出錯。`build_cf_html`/`parse_cf_html` 以純 Python 計算與還原它(往返測試、多位元組 UTF-8 正確);`set/get_clipboard_html` 將其包裝於 Win32 剪貼簿(含純文字後備)。位元組偏移運算可無頭測試;只有 I/O 為 Windows。
8+
39
## 本次更新 (2026-06-23) — 可串接 / 可過濾的候選定位器
410

511
用鏈式呼叫細化已定位的元素:`.within(panel).filter(has_text="Delete").nth(1)`。完整參考:[`docs/source/Zh/doc/new_features/v143_features_doc.rst`](../docs/source/Zh/doc/new_features/v143_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-23) — Rich Clipboard (HTML / CF_HTML)
4+
5+
Copy and paste *formatted* HTML into Word / Outlook. Full reference: [`docs/source/Eng/doc/new_features/v144_features_doc.rst`](docs/source/Eng/doc/new_features/v144_features_doc.rst).
6+
7+
- **`build_cf_html` / `parse_cf_html` / `set_clipboard_html` / `get_clipboard_html`** (`AC_set_clipboard_html`, `AC_get_clipboard_html`): the base clipboard handles plain text + image only — rich paste needs `CF_HTML`, whose byte-offset header (`StartHTML`/`EndHTML`/`StartFragment`/`EndFragment`) is famously error-prone. `build_cf_html`/`parse_cf_html` compute and recover it in pure Python (round-trip tested, correct across multi-byte UTF-8); `set/get_clipboard_html` wrap them over the Win32 clipboard (with a plain-text fallback). Byte-offset math is headless-testable; only the I/O is Windows.
8+
39
## What's new (2026-06-23) — Composable / Filtered Candidate Locators
410

511
Refine located elements with a chain: `.within(panel).filter(has_text="Delete").nth(1)`. Full reference: [`docs/source/Eng/doc/new_features/v143_features_doc.rst`](docs/source/Eng/doc/new_features/v143_features_doc.rst).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Rich Clipboard — HTML (CF_HTML)
2+
===============================
3+
4+
The base ``clipboard`` module handles plain text (``CF_UNICODETEXT``) and image
5+
(``CF_DIB``) only. Pasting *formatted* content into Word / Outlook / a rich editor
6+
needs the ``CF_HTML`` format, whose ``Version / StartHTML / EndHTML / StartFragment /
7+
EndFragment`` **byte-offset** header is notoriously error-prone to build by hand.
8+
``build_cf_html`` / ``parse_cf_html`` compute and recover that header in pure Python
9+
(a fully unit-tested round-trip, correct across multi-byte UTF-8), and
10+
``set_clipboard_html`` / ``get_clipboard_html`` wrap them over the Win32 clipboard.
11+
12+
The byte-offset math is platform-independent and headless-testable; only the actual
13+
clipboard I/O is Windows (raising ``RuntimeError`` elsewhere, like the base module).
14+
Imports no ``PySide6``.
15+
16+
Headless API
17+
------------
18+
19+
.. code-block:: python
20+
21+
from je_auto_control import (build_cf_html, parse_cf_html,
22+
set_clipboard_html, get_clipboard_html)
23+
24+
set_clipboard_html("<b>Bold</b> and <i>italic</i>",
25+
fragment_plaintext="Bold and italic") # Windows
26+
html = get_clipboard_html() # Windows
27+
28+
# The pure pieces work anywhere (e.g. to pre-build a payload):
29+
payload = build_cf_html("<p>hello</p>") # bytes, valid CF_HTML
30+
assert parse_cf_html(payload) == "<p>hello</p>"
31+
32+
``build_cf_html`` returns valid ``CF_HTML`` UTF-8 bytes whose offsets point exactly at
33+
the fragment; ``parse_cf_html`` recovers the fragment from bytes or text (preferring
34+
the comment markers, falling back to the byte offsets). ``set_clipboard_html`` also
35+
seeds plain text via ``fragment_plaintext`` so apps that ignore HTML still paste
36+
something.
37+
38+
Executor commands
39+
-----------------
40+
41+
``AC_set_clipboard_html`` (``html`` / ``fragment_plaintext`` → ``{set, length}``) and
42+
``AC_get_clipboard_html`` (→ ``{found, html}``). They are exposed as the MCP tools
43+
``ac_set_clipboard_html`` / ``ac_get_clipboard_html`` and as Script Builder commands
44+
under **Data**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ Comprehensive guides for all AutoControl features.
166166
doc/new_features/v141_features_doc
167167
doc/new_features/v142_features_doc
168168
doc/new_features/v143_features_doc
169+
doc/new_features/v144_features_doc
169170
doc/ocr_backends/ocr_backends_doc
170171
doc/observability/observability_doc
171172
doc/operations_layer/operations_layer_doc
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
豐富剪貼簿——HTML(CF_HTML)
2+
=============================
3+
4+
基礎 ``clipboard`` 模組只處理純文字(``CF_UNICODETEXT``)與影像(``CF_DIB``)。將*格式化*內容貼進 Word /
5+
Outlook / 富文字編輯器需要 ``CF_HTML`` 格式,其 ``Version / StartHTML / EndHTML / StartFragment /
6+
EndFragment`` **位元組偏移**標頭以手寫極易出錯。``build_cf_html`` / ``parse_cf_html`` 以純 Python 計算與還原
7+
該標頭(完整單元測試的往返,且在多位元組 UTF-8 下正確),而 ``set_clipboard_html`` / ``get_clipboard_html``
8+
將其包裝於 Win32 剪貼簿之上。
9+
10+
位元組偏移運算與平台無關且可無頭測試;只有實際的剪貼簿 I/O 為 Windows(在其他平台丟出 ``RuntimeError``,
11+
與基礎模組一致)。不匯入 ``PySide6``。
12+
13+
無頭 API
14+
--------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import (build_cf_html, parse_cf_html,
19+
set_clipboard_html, get_clipboard_html)
20+
21+
set_clipboard_html("<b>Bold</b> and <i>italic</i>",
22+
fragment_plaintext="Bold and italic") # Windows
23+
html = get_clipboard_html() # Windows
24+
25+
# 純函式在任何平台皆可用(例如預先建立 payload):
26+
payload = build_cf_html("<p>hello</p>") # bytes,有效 CF_HTML
27+
assert parse_cf_html(payload) == "<p>hello</p>"
28+
29+
``build_cf_html`` 回傳偏移精確指向片段的有效 ``CF_HTML`` UTF-8 位元組;``parse_cf_html`` 從 bytes 或文字還原片段
30+
(優先用註解標記,退而用位元組偏移)。``set_clipboard_html`` 也會以 ``fragment_plaintext`` 設定純文字,讓忽略
31+
HTML 的程式仍能貼上內容。
32+
33+
執行器命令
34+
----------
35+
36+
``AC_set_clipboard_html``(``html`` / ``fragment_plaintext`` → ``{set, length}``)與 ``AC_get_clipboard_html``
37+
(→ ``{found, html}``)。它們以 MCP 工具 ``ac_set_clipboard_html`` / ``ac_get_clipboard_html`` 以及 Script
38+
Builder 中 **Data** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ AutoControl 所有功能的完整使用指南。
166166
doc/new_features/v141_features_doc
167167
doc/new_features/v142_features_doc
168168
doc/new_features/v143_features_doc
169+
doc/new_features/v144_features_doc
169170
doc/ocr_backends/ocr_backends_doc
170171
doc/observability/observability_doc
171172
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,10 @@
335335
)
336336
# Composable / filtered candidate locators (chained-locator idiom)
337337
from je_auto_control.utils.locator_chain import Candidates, from_boxes
338+
# Rich clipboard formats — HTML (CF_HTML) build / parse / get / set
339+
from je_auto_control.utils.rich_clipboard import (
340+
build_cf_html, get_clipboard_html, parse_cf_html, set_clipboard_html,
341+
)
338342
# CI workflow annotations (GitHub Actions)
339343
from je_auto_control.utils.ci_annotations import (
340344
emit_annotations, format_annotation,
@@ -1187,6 +1191,10 @@ def start_autocontrol_gui(*args, **kwargs):
11871191
"to_be_stable",
11881192
"Candidates",
11891193
"from_boxes",
1194+
"build_cf_html",
1195+
"parse_cf_html",
1196+
"get_clipboard_html",
1197+
"set_clipboard_html",
11901198
"emit_annotations", "format_annotation",
11911199
"ClipboardHistory", "default_clipboard_history",
11921200
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,19 @@ def _add_native_control_specs(specs: List[CommandSpec]) -> None:
10441044

10451045
def _add_misc_specs(specs: List[CommandSpec]) -> None:
10461046
_add_native_control_specs(specs)
1047+
specs.append(CommandSpec(
1048+
"AC_set_clipboard_html", "Data", "Set Clipboard HTML",
1049+
fields=(
1050+
FieldSpec("html", FieldType.STRING,
1051+
placeholder="<b>Bold</b> rich text"),
1052+
FieldSpec("fragment_plaintext", FieldType.STRING, optional=True),
1053+
),
1054+
description="Put rich HTML on the clipboard (CF_HTML, Windows).",
1055+
))
1056+
specs.append(CommandSpec(
1057+
"AC_get_clipboard_html", "Data", "Get Clipboard HTML",
1058+
description="Read the clipboard's HTML fragment (CF_HTML, Windows).",
1059+
))
10471060
specs.append(CommandSpec(
10481061
"AC_watchdog_add", "Flow", "Watchdog: Add Popup Rule",
10491062
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3661,6 +3661,21 @@ def _locate_chain(boxes: Any, ops: Any = None) -> Dict[str, Any]:
36613661
"center": candidates.center()}
36623662

36633663

3664+
def _set_clipboard_html(html: str, fragment_plaintext: Any = None
3665+
) -> Dict[str, Any]:
3666+
"""Adapter: put an HTML fragment on the clipboard as CF_HTML (Windows)."""
3667+
from je_auto_control.utils.rich_clipboard import set_clipboard_html
3668+
set_clipboard_html(str(html), fragment_plaintext=fragment_plaintext)
3669+
return {"set": True, "length": len(str(html))}
3670+
3671+
3672+
def _get_clipboard_html() -> Dict[str, Any]:
3673+
"""Adapter: read the clipboard's HTML fragment (Windows)."""
3674+
from je_auto_control.utils.rich_clipboard import get_clipboard_html
3675+
html = get_clipboard_html()
3676+
return {"found": html is not None, "html": html}
3677+
3678+
36643679
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
36653680
"""Adapter: run nested actions while modifier keys are held down."""
36663681
import json
@@ -5402,6 +5417,8 @@ def __init__(self):
54025417
"AC_find_separators": _find_separators,
54035418
"AC_expect_poll": _expect_poll,
54045419
"AC_locate_chain": _locate_chain,
5420+
"AC_set_clipboard_html": _set_clipboard_html,
5421+
"AC_get_clipboard_html": _get_clipboard_html,
54055422
"AC_tile_rect": _tile_rect,
54065423
"AC_grid_rects": _grid_rects,
54075424
"AC_cascade_rects": _cascade_rects,

0 commit comments

Comments
 (0)