Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
9af54c51
Commit
9af54c51
authored
Aug 21, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pref: markdown渲染优化
parent
3b9cf83e
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
103 additions
and
15 deletions
+103
-15
MarkdownRenderer.cs
Tools/MyAgent/Support/MarkdownRenderer.cs
+101
-13
TextPatterns.cs
Tools/MyAgent/Support/TextPatterns.cs
+2
-2
No files found.
Tools/MyAgent/Support/MarkdownRenderer.cs
View file @
9af54c51
...
@@ -15,7 +15,8 @@ namespace Tools.MyAgent
...
@@ -15,7 +15,8 @@ namespace Tools.MyAgent
@"{\rtf1\ansi\deff0"
+
@"{\rtf1\ansi\deff0"
+
@"{\fonttbl"
+
@"{\fonttbl"
+
@"{\f0\fnil\fcharset134 Microsoft YaHei;}"
+
@"{\f0\fnil\fcharset134 Microsoft YaHei;}"
+
@"{\f1\fnil\fcharset134 Courier New;}}"
+
@"{\f1\fnil\fcharset134 Courier New;}"
+
@"{\f2\fnil\fcharset0 Segoe UI Emoji;}}"
+
// emoji 回退字体(D1/D3:微软雅黑无 emoji 字形)
@"{\colortbl"
+
@"{\colortbl"
+
@";\red0\green120\blue212"
+
// 1: user blue
@";\red0\green120\blue212"
+
// 1: user blue
@";\red128\green128\blue128"
+
// 2: timestamp gray
@";\red128\green128\blue128"
+
// 2: timestamp gray
...
@@ -162,6 +163,7 @@ namespace Tools.MyAgent
...
@@ -162,6 +163,7 @@ namespace Tools.MyAgent
var
lines
=
markdown
.
Split
(
new
[]
{
"\r\n"
,
"\n"
},
StringSplitOptions
.
None
);
var
lines
=
markdown
.
Split
(
new
[]
{
"\r\n"
,
"\n"
},
StringSplitOptions
.
None
);
bool
inCodeBlock
=
false
;
bool
inCodeBlock
=
false
;
bool
codeHasCjk
=
false
;
// 代码块是否含 CJK(在收集阶段检测原始文本;转义后文本无法检测)(D5)
var
codeBuf
=
new
StringBuilder
();
var
codeBuf
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
lines
.
Length
;
i
++)
for
(
int
i
=
0
;
i
<
lines
.
Length
;
i
++)
...
@@ -211,8 +213,9 @@ namespace Tools.MyAgent
...
@@ -211,8 +213,9 @@ namespace Tools.MyAgent
else
else
{
{
inCodeBlock
=
false
;
inCodeBlock
=
false
;
RenderCodeBlock
(
sb
,
codeBuf
.
ToString
());
RenderCodeBlock
(
sb
,
codeBuf
.
ToString
()
,
codeHasCjk
);
codeBuf
.
Clear
();
codeBuf
.
Clear
();
codeHasCjk
=
false
;
}
}
continue
;
continue
;
}
}
...
@@ -221,6 +224,8 @@ namespace Tools.MyAgent
...
@@ -221,6 +224,8 @@ namespace Tools.MyAgent
{
{
if
(
codeBuf
.
Length
>
0
)
if
(
codeBuf
.
Length
>
0
)
codeBuf
.
Append
(
@"\line "
);
codeBuf
.
Append
(
@"\line "
);
if
(
ContainsCjk
(
line
))
codeHasCjk
=
true
;
// 原始行检测(转义后为 \uXXXX? 序列,无法用正则命中 CJK)
codeBuf
.
Append
(
Escape
(
line
));
codeBuf
.
Append
(
Escape
(
line
));
continue
;
continue
;
}
}
...
@@ -293,7 +298,7 @@ namespace Tools.MyAgent
...
@@ -293,7 +298,7 @@ namespace Tools.MyAgent
if
(
inCodeBlock
)
if
(
inCodeBlock
)
{
{
RenderCodeBlock
(
sb
,
codeBuf
.
ToString
());
RenderCodeBlock
(
sb
,
codeBuf
.
ToString
()
,
codeHasCjk
);
}
}
}
}
...
@@ -338,13 +343,16 @@ namespace Tools.MyAgent
...
@@ -338,13 +343,16 @@ namespace Tools.MyAgent
}
}
}
}
private
static
void
RenderCodeBlock
(
StringBuilder
sb
,
string
code
)
private
static
void
RenderCodeBlock
(
StringBuilder
sb
,
string
code
,
bool
hasCjk
)
{
{
string
trimmed
=
code
.
Trim
();
string
trimmed
=
code
.
Trim
();
// 代码字体:含 CJK 时切回微软雅黑(Courier New 无 CJK 字形,依赖系统字体链接不可靠)(D5)
// 注意:code 为已转义文本(\uXXXX?),CJK 判定必须由调用方在收集阶段基于原始行完成
string
codeFont
=
hasCjk
?
@"\f0"
:
@"\f1"
;
if
(!
trimmed
.
Contains
(
"\n"
)
&&
!
trimmed
.
Contains
(
"\r"
)
&&
IsLikelyCommand
(
trimmed
))
if
(!
trimmed
.
Contains
(
"\n"
)
&&
!
trimmed
.
Contains
(
"\r"
)
&&
IsLikelyCommand
(
trimmed
))
{
{
sb
.
Append
(
@"\pard\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
@"\pard\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
@"\f1
\fs18 "
);
sb
.
Append
(
codeFont
);
sb
.
Append
(
@"
\fs18 "
);
sb
.
Append
(
BuildCmdHyperlink
(
trimmed
));
sb
.
Append
(
BuildCmdHyperlink
(
trimmed
));
sb
.
Append
(
@"\highlight0\f0\fs20\par "
);
sb
.
Append
(
@"\highlight0\f0\fs20\par "
);
return
;
return
;
...
@@ -352,7 +360,7 @@ namespace Tools.MyAgent
...
@@ -352,7 +360,7 @@ namespace Tools.MyAgent
sb
.
Append
(
@"\pard\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
@"\pard\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
@"\cf"
);
sb
.
Append
(
ClCodeFg
);
sb
.
Append
(
@"\cf"
);
sb
.
Append
(
ClCodeFg
);
sb
.
Append
(
@"\f1
\fs18 "
);
sb
.
Append
(
codeFont
);
sb
.
Append
(
@"
\fs18 "
);
sb
.
Append
(
code
);
sb
.
Append
(
code
);
sb
.
Append
(
@"\highlight0\cf"
);
sb
.
Append
(
ClText
);
sb
.
Append
(
@"\highlight0\cf"
);
sb
.
Append
(
ClText
);
sb
.
Append
(
@"\f0\fs20\par "
);
sb
.
Append
(
@"\f0\fs20\par "
);
...
@@ -412,16 +420,18 @@ namespace Tools.MyAgent
...
@@ -412,16 +420,18 @@ namespace Tools.MyAgent
if
(
end
>
i
)
if
(
end
>
i
)
{
{
string
code
=
text
.
Substring
(
i
+
1
,
end
-
i
-
1
);
string
code
=
text
.
Substring
(
i
+
1
,
end
-
i
-
1
);
// 含 CJK 时切微软雅黑(Courier New 无 CJK 字形)(D5)
string
codeFont
=
ContainsCjk
(
code
)
?
@"\f0"
:
@"\f1"
;
if
(
IsLikelyCommand
(
code
))
if
(
IsLikelyCommand
(
code
))
{
{
sb
.
Append
(
@"{\f1
\fs18\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
"{"
);
sb
.
Append
(
codeFont
);
sb
.
Append
(
@"
\fs18\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
" "
);
sb
.
Append
(
" "
);
sb
.
Append
(
BuildCmdHyperlink
(
code
));
sb
.
Append
(
BuildCmdHyperlink
(
code
));
sb
.
Append
(
@"\highlight0\f0\fs20}"
);
sb
.
Append
(
@"\highlight0\f0\fs20}"
);
}
}
else
else
{
{
sb
.
Append
(
@"{\f1
\fs18\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
"{"
);
sb
.
Append
(
codeFont
);
sb
.
Append
(
@"
\fs18\highlight"
);
sb
.
Append
(
ClCodeBg
);
sb
.
Append
(
@"\cf"
);
sb
.
Append
(
ClCodeFg
);
sb
.
Append
(
@"\cf"
);
sb
.
Append
(
ClCodeFg
);
sb
.
Append
(
" "
);
sb
.
Append
(
" "
);
sb
.
Append
(
Escape
(
code
));
sb
.
Append
(
Escape
(
code
));
...
@@ -509,6 +519,14 @@ namespace Tools.MyAgent
...
@@ -509,6 +519,14 @@ namespace Tools.MyAgent
continue
;
continue
;
}
}
// 非 BMP 代理对(4 字节 emoji):切 Segoe UI Emoji 字体以 \uN 双转义组合渲染(D1)
if
(
char
.
IsHighSurrogate
(
text
[
i
])
&&
i
+
1
<
text
.
Length
&&
char
.
IsLowSurrogate
(
text
[
i
+
1
]))
{
AppendSurrogatePair
(
sb
,
text
[
i
],
text
[
i
+
1
]);
i
++;
continue
;
}
sb
.
Append
(
EscapeChar
(
text
[
i
]));
sb
.
Append
(
EscapeChar
(
text
[
i
]));
i
++;
i
++;
}
}
...
@@ -524,11 +542,36 @@ namespace Tools.MyAgent
...
@@ -524,11 +542,36 @@ namespace Tools.MyAgent
{
{
if
(
string
.
IsNullOrEmpty
(
text
))
return
""
;
if
(
string
.
IsNullOrEmpty
(
text
))
return
""
;
var
sb
=
new
StringBuilder
(
text
.
Length
);
var
sb
=
new
StringBuilder
(
text
.
Length
);
foreach
(
char
c
in
text
)
AppendEscaped
(
sb
,
text
);
sb
.
Append
(
EscapeChar
(
c
));
return
sb
.
ToString
();
return
sb
.
ToString
();
}
}
/// <summary>
/// 逐字符转义;非 BMP 代理对(4 字节 emoji)切 Segoe UI Emoji 字体并以 \uN 双转义组合渲染(D1),
/// 避免被丢弃导致内容缺失。
/// </summary>
private
static
void
AppendEscaped
(
StringBuilder
sb
,
string
text
)
{
for
(
int
i
=
0
;
i
<
text
.
Length
;
i
++)
{
char
c
=
text
[
i
];
if
(
char
.
IsHighSurrogate
(
c
)
&&
i
+
1
<
text
.
Length
&&
char
.
IsLowSurrogate
(
text
[
i
+
1
]))
{
AppendSurrogatePair
(
sb
,
c
,
text
[
i
+
1
]);
i
++;
continue
;
}
sb
.
Append
(
EscapeChar
(
c
));
}
}
/// <summary>代理对(4 字节 emoji):切 Segoe UI Emoji 字体以 \uN 双转义组合渲染(D1)</summary>
private
static
void
AppendSurrogatePair
(
StringBuilder
sb
,
char
high
,
char
low
)
{
sb
.
Append
(
@"{\f2 \u"
).
Append
(
unchecked
((
short
)
high
)).
Append
(
'?'
)
.
Append
(
@"\u"
).
Append
(
unchecked
((
short
)
low
)).
Append
(
"?}"
);
}
private
static
string
EscapeChar
(
char
c
)
private
static
string
EscapeChar
(
char
c
)
{
{
switch
(
c
)
switch
(
c
)
...
@@ -540,15 +583,60 @@ namespace Tools.MyAgent
...
@@ -540,15 +583,60 @@ namespace Tools.MyAgent
case
'\n'
:
return
@"\line "
;
case
'\n'
:
return
@"\line "
;
case
'\t'
:
return
@"\tab "
;
case
'\t'
:
return
@"\tab "
;
default
:
default
:
// 代理对(emoji 等非 BMP 字符):RTF \uN 无法表达,直接丢弃避免渲染为 "??"
// C0 控制字符(除已处理)与 DEL:无展示价值,直接丢弃,避免原始控制字节破坏 RTF 解析(D2)
if
(
char
.
IsSurrogate
(
c
))
if
(
c
<
0x20
||
c
==
0x7F
)
return
""
;
return
""
;
// 孤立代理(数据损坏):丢弃,避免渲染残缺(D1 兜底)
if
(
char
.
IsSurrogate
(
c
))
return
""
;
// 零宽/不可见格式字符(D4):丢弃,避免在无字形字体下显示为缺字形框
if
(
IsInvisibleFormatChar
(
c
))
return
""
;
// 非 ASCII:\uN 转义(头部已声明 \uc1,后跟 1 个回退字符),
// 非 ASCII:\uN 转义(头部已声明 \uc1,后跟 1 个回退字符),
// 避免字符不在系统 ANSI 代码页中而被 RichTextBox 渲染为 '?'
// 避免字符不在系统 ANSI 代码页中而被 RichTextBox 渲染为 '?'
if
(
c
>=
128
)
if
(
c
>=
128
)
{
// emoji/符号区段(D3):微软雅黑缺字形 → 切 Segoe UI Emoji 渲染,避免显示 □
if
(
IsEmojiSymbol
(
c
))
return
$@"
{{
\
f2
\
u
{
unchecked
((
short
)
c
)}?}}
"
;
return
$@"\u
{
unchecked
((
short
)
c
)}
?"
;
return
$@"\u
{
unchecked
((
short
)
c
)}
?"
;
}
return
c
.
ToString
();
return
c
.
ToString
();
}
}
}
}
/// <summary>零宽/不可见格式字符(D4):无展示价值且多数字体缺字形,丢弃避免显示 □</summary>
private
static
bool
IsInvisibleFormatChar
(
char
c
)
{
switch
(
c
)
{
case
'\
u00AD
'
:
// 软连字符
case
'\
u200B
'
:
// 零宽空格
case
'\
u200C
'
:
// 零宽非连接符
case
'\
u200D
'
:
// 零宽连接符 ZWJ(组合 emoji 拆散后残留,单独无展示价值)
case
'\
u200E
'
:
// 从左到右标记
case
'\
u200F
'
:
// 从右到左标记
case
'\
u2060
'
:
// 字词连接符
case
'\
uFE0F
'
:
// 变体选择符 VS16(emoji 已切 \f2 字体呈现,无需修饰符;丢弃避免残留字符)
case
'\
uFEFF
'
:
// BOM / 零宽不换行空格
return
true
;
default
:
return
false
;
}
}
/// <summary>微软雅黑缺字形的 BMP emoji/符号区段(D3):切 Segoe UI Emoji 渲染(U+FE0F 变体选择符在 IsInvisibleFormatChar 中直接丢弃)</summary>
private
static
bool
IsEmojiSymbol
(
char
c
)
{
return
(
c
>=
0x2600
&&
c
<=
0x27BF
)
// 杂项符号 + 装饰符号(含 ⚠ ✅ ⭐ 等)
||
(
c
>=
0x2B00
&&
c
<=
0x2BFF
);
// 杂项符号和箭头(含 ⬆ ⬇)
}
/// <summary>代码内容是否含 CJK(D5):Courier New 无 CJK 字形,含 CJK 的代码块/行内代码切回微软雅黑</summary>
private
static
readonly
Regex
CjkPattern
=
new
Regex
(
@"[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\u3000-\u303F\uFF00-\uFFEF]"
,
RegexOptions
.
Compiled
);
private
static
bool
ContainsCjk
(
string
text
)
{
return
!
string
.
IsNullOrEmpty
(
text
)
&&
CjkPattern
.
IsMatch
(
text
);
}
}
}
}
}
Tools/MyAgent/Support/TextPatterns.cs
View file @
9af54c51
...
@@ -13,9 +13,9 @@ namespace Tools.MyAgent
...
@@ -13,9 +13,9 @@ namespace Tools.MyAgent
@"\b([A-Za-z]:[\\/](?:[^\s\\/:*?""<>|]+[\\/])*[^\s\\/:*?""<>|]+\.[a-zA-Z0-9]{1,10})\b"
,
@"\b([A-Za-z]:[\\/](?:[^\s\\/:*?""<>|]+[\\/])*[^\s\\/:*?""<>|]+\.[a-zA-Z0-9]{1,10})\b"
,
RegexOptions
.
Compiled
);
RegexOptions
.
Compiled
);
/// <summary>UNC 网络路径 \\server\share\...(到空白/引号/常见句读标点为止)</summary>
/// <summary>UNC 网络路径 \\server\share\...(到空白/引号/常见句读标点为止
;末尾英文句号视为句读,用负向后瞻排除——文件名中的句号不受影响,如 file.md
)</summary>
public
static
readonly
Regex
UncFilePath
=
new
Regex
(
public
static
readonly
Regex
UncFilePath
=
new
Regex
(
@"\\\\[^\s<\"",。;:、!?))】》>]+"
,
@"\\\\[^\s<\"",。;:、!?))】》>]+
(?<![.])
"
,
RegexOptions
.
Compiled
);
RegexOptions
.
Compiled
);
/// <summary>http/https 链接</summary>
/// <summary>http/https 链接</summary>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment