raw.tin 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #nop vim: set filetype=tt:;
  2. /*
  3. 本文件属于 PaoTin++ 的一部分。
  4. PaoTin++ © 2020~2026 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
  5. 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
  6. */
  7. #nop 本文件是 xtintin 的一部分,实现了一些颜色字符串处理函数;
  8. ///=== {
  9. ///// 颜色字符串处理函数:
  10. //
  11. // #@ str.Plain <字符串>
  12. // 去除字符串中的颜色代码和 ANSI 控制字符。
  13. //
  14. // #@ str.Color <字符串>
  15. // 仅保留字符串中的颜色代码,去掉其余成分,方便比较颜色。
  16. // 本函数与 str.SimpleColor 的区别在于,本函数不会简化颜色。
  17. //
  18. // #@ str.SimpleColor <字符串>
  19. // 仅保留字符串中的颜色代码,去掉其余成分,方便比较颜色。
  20. // 本函数与 str.Color 的区别在于,本函数会简化颜色。
  21. // 所谓简化是指对颜色代码串进行了 color.Trim 和 color.Clean 操作。
  22. //
  23. // #@ color.Trim <颜色代码串>
  24. // 去掉颜色代码串两头的复位代码。
  25. //
  26. // #@ color.Clean <颜色代码串>
  27. // 压缩颜色代码串,只保留经过运算后的效果。
  28. // NOTE: 注意某些情况下你可能需要先进行 color.Trim 操作。
  29. // };
  30. #func {str.Plain} {#format result {%P} {%0}};
  31. #func {str.Color} {
  32. #local color {};
  33. #local state {START};
  34. #local ch {};
  35. #parse {%0} {ch} {
  36. #switch {"$state/$ch"} {
  37. #case {"START/\e"} {#local state {ESCAPE}};
  38. #case {"START/%*"} {#0};
  39. #case {"ESCAPE/["} {#local state {INNER}};
  40. #case {"ESCAPE/%*"} {#local state {OUTER}};
  41. #case {"INNER/m"} {#cat color {;}; #local state {OUTER}};
  42. #case {"INNER/%*"} {#cat color {$ch}};
  43. #case {"OUTER/\e"} {#local state {ESCAPE}};
  44. };
  45. };
  46. #replace {color} {;$} {};
  47. #return {$color};
  48. };
  49. #func {str.SimpleColor} {
  50. #local color {@str.Color{%0}};
  51. #local color {@color.Trim{$color}};
  52. #local color {@color.Clean{$color}};
  53. #return {$color};
  54. };
  55. #func {color.Trim} {
  56. #local color {%0};
  57. #replace color {^{(2;37;0(;|))+}} {};
  58. #replace color {^{(0(;|))+}} {};
  59. #replace color {{((;|)2;37;0)+}$} {};
  60. #replace color {{(0(;|))+}$} {};
  61. #return {$color};
  62. };
  63. #func {color.Clean} {
  64. #local color {%0};
  65. #local str {};
  66. #line sub escapes #format str {%s} {@raw.ParseFragments{{\e[${color}m}}};
  67. #if { &str[color][] == 0 } {
  68. #return {};
  69. };
  70. #local color {$str[color]};
  71. #local attr {};
  72. #local output {};
  73. #foreach {$color[attrs]} {attr} {
  74. #switch {"$attr"} {
  75. #case {"bold"} {#cat output {;1}};
  76. #case {"dim"} {#cat output {;2}};
  77. #case {"italic"} {#cat output {;3}};
  78. #case {"underline"} {#cat output {;4}};
  79. #case {"blink"} {#cat output {;5}};
  80. #case {"reverse"} {#cat output {;7}};
  81. #case {"f16"} {#cat output {;$color[fg]}};
  82. #case {"b16"} {#cat output {;$color[bg]}};
  83. #case {"F16"} {#cat output {;$color[fg]}};
  84. #case {"B16"} {#cat output {;$color[bg]}};
  85. #case {"f256"} {#cat output {;38;5;$color[fg]}};
  86. #case {"b256"} {#cat output {;48;5;$color[bg]}};
  87. #case {"ftrue"} {#cat output {;38;2;$color[fg]}};
  88. #case {"btrue"} {#cat output {;48;2;$color[bg]}};
  89. };
  90. };
  91. #replace output {^;} {};
  92. #return {$output};
  93. };
  94. ///=== {
  95. // #@ util.WordColor <字符串> <子字符串>
  96. // 从字符串中检索子字符串,如果找到它,就把它的颜色取出来并作为返回值,否则返回空。
  97. // };
  98. #func {str.WordColor} {
  99. #local str {%1};
  100. #local word {%2};
  101. #replace word {%+1u} {(?:\e\[[0-9;:]*m)*&1};
  102. #replace str {^%*{$word}%*$} {&2};
  103. #local color {@str.Color{$str}};
  104. #replace color {^0;} {};
  105. #replace color {;0$} {};
  106. #replace color {2;37;0;} {};
  107. #replace color {;2;37;0} {};
  108. #return {$color};
  109. };
  110. ///=== {
  111. // #@ util.ColorBar <字符串> <颜色1> <权重1> <颜色2> <权重2> [...]
  112. // 将字符串按照颜色及其对应的权重占比,渲染成彩色字符串。注意颜色参数须按顺序排列。
  113. // };
  114. #func {util.ColorBar} {
  115. #local str {%1};
  116. #local args {};
  117. #list args create {%0};
  118. #list args delete {1} {1};
  119. #local parts {};
  120. #list parts create {};
  121. #local count {0};
  122. #local sum {0};
  123. #while { $count < &args[] } {
  124. #local color {$args[@math.Eval{$count + 1}]};
  125. #local weight {@defaultNum{$args[@math.Eval{$count + 2}];0}};
  126. #list parts {add} {{
  127. {color}{$color}
  128. {weight}{$weight}
  129. }};
  130. #math count {$count + 2};
  131. #math sum {$sum + $weight};
  132. };
  133. #local elem {};
  134. #local len {@str.Len{$str}};
  135. #local leftLen {0};
  136. #local leftWeight {0};
  137. #local colorStr {};
  138. #foreach {$parts[%*]} {elem} {
  139. #local elemLen {@math.Eval{($elem[weight] + $leftWeight) * $len / $sum - $leftLen}};
  140. #local text {@str.Sub{{$str};$leftLen;$elemLen}};
  141. #cat colorStr {$elem[color]$text<299>};
  142. #math leftLen {$leftLen + $elemLen};
  143. #math leftWeight {$leftWeight + $elem[weight]};
  144. };
  145. #return {$colorStr};
  146. };
  147. ///=== {
  148. // #@ raw.ParseFragments <颜色字符串> [<之前的颜色>]
  149. // 解析包含颜色代码的字符串,将其拆分成一系列的文本片段,每个片段包含一段文本和对应的颜色属性。
  150. // 本函数实际上有两个返回值,以 table 形式打包返回,其中包含以下内容:
  151. // - color: 字符串解析完毕之后,最后的颜色状态(可供之后调用),参见下面的 color 属性。
  152. // - fragments: 一个表格列表,其中每个元素是一个表格,包含以下两个字段:
  153. // - text: 文本片段
  154. // - color:
  155. // - fg: 前景色,可能是一个一位数(16色系统)或一个多位数(256色系统)或三个数(RGB颜色)
  156. // - bg: 背景色,同上
  157. // - attrs: 文本样式属性,是一个字符串集合(sset),包含以下可能的值:
  158. // - bold
  159. // - dim
  160. // - italic
  161. // - underline
  162. // - blink
  163. // - reverse
  164. // - f16 表示前景色是 16 色系统
  165. // - b16 表示背景色是 16 色系统
  166. // - F16 表示前景色是 AIXterm 16 色系统
  167. // - B16 表示背景色是 AIXterm 16 色系统
  168. // - f256 表示前景色是 256 色系统
  169. // - b256 表示背景色是 256 色系统
  170. // - ftrue 表示前景色是真彩色
  171. // - btrue 表示背景色是真彩色
  172. // 注意以上属性承诺按照以上顺序出现,但如果未设置该属性则不出现。
  173. // };
  174. #func {raw.ParseFragments} {
  175. #local str {%1};
  176. #local color {%2};
  177. #local fragments {};
  178. #local fragment {};
  179. #local tokens {};
  180. #local mode {TEXT};
  181. #while {1} {
  182. #if { &tokens[] == 0 } {
  183. raw.ParseFragments.nextToken;
  184. #if { "$str" === "" } {
  185. #break;
  186. };
  187. #if { "$str[text]" !== "" } {
  188. #local fragment[text] {$str[text]};
  189. #local fragment[color] {@raw.ParseFragments.buildColor{$color}};
  190. #list fragments add {{$fragment}};
  191. #local fragment {};
  192. };
  193. #elseif { "$str[color]" !== "" } {
  194. #replace str[color] {^\e[} {};
  195. #replace str[color] {m$} {};
  196. #if { "$str[color]" !== "" } {
  197. #list tokens add {$str[color]};
  198. #local mode {SGR};
  199. };
  200. };
  201. #local str {$str[rest]};
  202. #continue;
  203. };
  204. #local token {$tokens[1]};
  205. #list tokens delete 1;
  206. #switch {"$mode/$token"} {
  207. #case {"SGR/0"} {#local color[fg] {}; #local color[bg] {}; #local color[attrs] {}};
  208. #case {"SGR/1"} {#local color[attrs][bold] {1}};
  209. #case {"SGR/2"} {#local color[attrs][dim] {1}};
  210. #case {"SGR/3"} {#local color[attrs][italic] {1}};
  211. #case {"SGR/4"} {#local color[attrs][underline] {1}};
  212. #case {"SGR/5"} {#local color[attrs][blink] {1}};
  213. #case {"SGR/7"} {#local color[attrs][reverse] {1}};
  214. #case {"SGR/3{[0-7]}"} {#local color[fg] {$token}; #local color[attrs][fcolors] {8}};
  215. #case {"SGR/4{[0-7]}"} {#local color[bg] {$token}; #local color[attrs][bcolors] {8}};
  216. #case {"SGR/9{[0-7]}"} {#local color[fg] {$token}; #local color[attrs][fcolors] {16}};
  217. #case {"SGR/10{[0-7]}"} {#local color[bg] {$token}; #local color[attrs][bcolors] {16}};
  218. #case {"SGR/38"} {#local mode {SGR38}};
  219. #case {"SGR38/2"} {#local mode {SGR382}; #local color[fg] {}; #local color[attrs][fcolors] {256}};
  220. #case {"SGR382/%+1..d"} {#local mode {SGR}; #local color[fg] {$token}};
  221. #case {"SGR38/5"} {#local mode {SGR385}; #local color[fg] {}; #local color[attrs][fcolors] {true}};
  222. #case {"SGR385/%+1..d"} {#local mode {SGR3851}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  223. #case {"SGR3851/%+1..d"} {#local mode {SGR3852}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  224. #case {"SGR3852/%+1..d"} {#local mode {SGR}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  225. #case {"SGR/48"} {#local mode {SGR48}};
  226. #case {"SGR48/2"} {#local mode {SGR482}; #local color[bg] {}; #local color[attrs][bcolors] {256}};
  227. #case {"SGR482/%+1..d"} {#local mode {SGR}; #local color[bg] {$token}};
  228. #case {"SGR48/5"} {#local mode {SGR485}; #local color[bg] {}; #local color[attrs][bcolors] {true}};
  229. #case {"SGR485/%+1..d"} {#local mode {SGR4851}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  230. #case {"SGR4851/%+1..d"} {#local mode {SGR4852}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  231. #case {"SGR4852/%+1..d"} {#local mode {SGR}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  232. };
  233. };
  234. #local color {@raw.ParseFragments.buildColor{$color}};
  235. #if { "$str[text]" !== "" } {
  236. #local fragment[text] {$str[text]};
  237. #local fragment[color] {$color};
  238. #list fragments add {{$fragment}};
  239. };
  240. #return {
  241. {fragments} {$fragments}
  242. {color} {$color}
  243. };
  244. };
  245. #func {raw.ParseFragments.buildColor} {
  246. #local color {%0};
  247. #local attrs {$color[attrs]};
  248. #local attr {};
  249. #local newAttrs {};
  250. #foreach {bold;dim;italic;underline;blink;reverse} {attr} {
  251. #if { $attrs[$attr] } {
  252. #cat newAttrs {;$attr};
  253. };
  254. };
  255. #nop 前景色只能有一个系统,以最后出现的为准。;
  256. #switch {"$color[attrs][fcolors]/@slist.Size{$color[fg]}"} {
  257. #case {"8/1"} {#cat newAttrs {;f16}};
  258. #case {"16/1"} {#cat newAttrs {;F16}};
  259. #case {"256/1"} {#cat newAttrs {;f256}};
  260. #case {"true/3"} {#cat newAttrs {;ftrue}};
  261. };
  262. #nop 背景色只能有一个系统,以最后出现的为准。;
  263. #switch {"$color[attrs][bcolors]/@slist.Size{$color[bg]}"} {
  264. #case {"8/1"} {#cat newAttrs {;b16}};
  265. #case {"16/1"} {#cat newAttrs {;B16}};
  266. #case {"256/1"} {#cat newAttrs {;b256}};
  267. #case {"true/3"} {#cat newAttrs {;btrue}};
  268. };
  269. #replace newAttrs {^;} {};
  270. #local color[attrs] {$newAttrs};
  271. #return {$color};
  272. };
  273. #alias {raw.ParseFragments.nextToken} {
  274. #if { "$str" === "" } {
  275. #return;
  276. };
  277. #replace str {^{[^\e]+}%*$} {
  278. {text} {&1}
  279. {rest} {&2}
  280. };
  281. #var str {$str};
  282. #if { "$str[text]" !== "" } {
  283. #return {$str};
  284. };
  285. #replace str {^%+1c%*$} {
  286. {color} {&1}
  287. {rest} {&2}
  288. };
  289. #var str {$str};
  290. #if { "$str[text]" !== "" } {
  291. #return {$str};
  292. };
  293. #replace str {^\e%*} {
  294. {esc} {&1}
  295. {rest} {&2}
  296. };
  297. #var str {$str};
  298. };
  299. ///=== {
  300. // #@ raw.EraseInvisible <颜色字符串> [<是否压缩>]
  301. // 解析包含颜色代码的字符串,将其拆分成多个片段,并擦掉其中前景色与背景色相同的部分。
  302. // 如果同时指定了第二个参数为真,则将颜色相同的连续多个部分进行合并。
  303. // 最终返回值格式与 raw.ParseFragments 相同。
  304. // };
  305. #func {raw.EraseInvisible} {
  306. #local str {%1};
  307. #local zip {%2};
  308. #local color {};
  309. #local output {};
  310. #local str {@raw.ParseFragments{{$str}}};
  311. #foreach {*str[fragments][]} {fragment} {
  312. #local fragment {$str[fragments][$fragment]};
  313. #if { ! @sset.Contains{{$fragment[color][attrs]};f256}
  314. && ! @sset.Contains{{$fragment[color][attrs]};b256}
  315. && ! @sset.Contains{{$fragment[color][attrs]};ftrue}
  316. && ! @sset.Contains{{$fragment[color][attrs]};btrue}
  317. && $fragment[color][fg] + 10 == $fragment[color][bg] } {
  318. #continue;
  319. };
  320. #if { @sset.Contains{{$fragment[color][attrs]};f256}
  321. && @sset.Contains{{$fragment[color][attrs]};b256}
  322. && "$fragment[color][fg]" === "$fragment[color][bg]" } {
  323. #continue;
  324. };
  325. #if { @sset.Contains{{$fragment[color][attrs]};ftrue}
  326. && @sset.Contains{{$fragment[color][attrs]};btrue}
  327. && "$fragment[color][fg]" === "$fragment[color][bg]" } {
  328. #continue;
  329. };
  330. #if { @isTrue{$zip} && "$color" != "" && "$fragment[color]" === "$color" } {
  331. #cat output[-1][text] {$fragment[text]};
  332. };
  333. #else {
  334. #list output add {{$fragment}};
  335. #local color {$fragment[color]};
  336. };
  337. };
  338. #return {
  339. {fragments} {$output}
  340. {color} {$color}
  341. };
  342. };