Files
fanlusky 45c473158a feat(engine,server,windows,ui-html): 双拼预编辑可显示转换后的全拼
外观里行内预编辑与候选窗预编辑各加一个「双拼显示转换后的全拼」开关,默认关闭。
开启后在双拼方案下:行内原始按键显示不带分词符号的全拼,拼音分词显示带分词符号的
全拼;候选窗显示带分词符号的全拼。

- 引擎新增 get_shuangpin_preedit_forms,同时给出原串切分与全拼切分,不受
  shuangpin_preedit_mode 影响。
- 原始按键样式下 TSF 不等 Server 回包,开关开启且为双拼时 Server 下发的实际样式
  改为 pinyin,回包里再按用户样式去掉分词符号;换方案时重新下发。
- 转换后字母数与按键不再对应,Server 在预编辑末尾附光标映射表
  (engine/contracts/preedit_caret_map.h),DLL 用它定位光标和处理方向键,
  无表或长度不符时回落到原有字母映射。
2026-10-02 16:14:50 +08:00

91 lines
2.6 KiB
C++

#pragma once
#include <cstddef>
#include <string>
#include <vector>
// Server 回给 TSF 的行内预编辑可以在末尾附一张光标映射表。
//
// TSF 平时按「字母个数」把按键缓冲里的光标映射到预编辑上,这要求预编辑和按键缓冲是同一串字母、
// 只多了分词符号。双拼显示转换后的全拼时不成立(hc 显示成 hao),只有 Server 知道每个按键位置
// 落在哪个音节,所以由它把整张表带过来:
//
// 预编辑 U+E001 p0,p1,...,pN
//
// pi 是按键缓冲光标停在 i(0..N,N 为按键缓冲长度)时预编辑里的光标位置,十进制、逗号分隔。
// 没有表的回包和旧 Server 的回包照旧解释;表的条数和按键缓冲对不上时 TSF 回落到字母映射。
// 回包可能被 Tab 分段(UILess、造词),表里不会出现 Tab。
namespace FanyImePreeditCaretMap
{
inline constexpr wchar_t kMarker = L'\uE001';
inline std::wstring Encode(const std::wstring &preedit, const std::vector<std::size_t> &caret_map)
{
if (caret_map.empty())
{
return preedit;
}
std::wstring encoded = preedit;
encoded.push_back(kMarker);
for (std::size_t i = 0; i < caret_map.size(); ++i)
{
if (i > 0)
{
encoded.push_back(L',');
}
encoded += std::to_wstring(caret_map[i]);
}
return encoded;
}
// 把 payload 里的映射表拆出来,payload 只留下预编辑本身。没有表或表写坏了时 caret_map 为空。
inline void Decode(std::wstring &payload, std::vector<std::size_t> &caret_map)
{
caret_map.clear();
const std::size_t marker = payload.find(kMarker);
if (marker == std::wstring::npos)
{
return;
}
std::size_t value = 0;
bool has_digit = false;
bool valid = true;
for (std::size_t i = marker + 1; i < payload.size() && valid; ++i)
{
const wchar_t ch = payload[i];
if (ch >= L'0' && ch <= L'9')
{
value = value * 10 + static_cast<std::size_t>(ch - L'0');
has_digit = true;
}
else if (ch == L',' && has_digit)
{
caret_map.push_back(value);
value = 0;
has_digit = false;
}
else
{
valid = false;
}
}
if (valid && has_digit)
{
caret_map.push_back(value);
}
else
{
caret_map.clear();
}
payload.erase(marker);
for (const std::size_t position : caret_map)
{
if (position > payload.size())
{
caret_map.clear();
break;
}
}
}
} // namespace FanyImePreeditCaretMap