raw.tin 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #nop vim: set filetype=tt:;
  2. /*
  3. 本文件属于 PaoTin++ 的一部分。
  4. PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
  5. 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
  6. */
  7. #nop 本文件是 xtintin 的一部分,实现了一些颜色字符串处理函数;
  8. ///=== {
  9. ///// 颜色字符串处理函数:
  10. //
  11. // #@ str.Plain <字符串>
  12. // 去除字符串中的颜色代码和 ANSI 控制字符。
  13. //
  14. // #@ str.Color <字符串>
  15. // 仅保留字符串中的颜色代码,去掉其余成分,方便比较颜色。
  16. // };
  17. #func {str.Plain} {#format result {%P} {%0}};
  18. #func {str.Color} {
  19. #local str {};
  20. #local state {START};
  21. #local ch {};
  22. #parse {%0} {ch} {
  23. #switch {"$state/$ch"} {
  24. #case {"START/\e"} {#local state {ESCAPE}};
  25. #case {"START/%*"} {#0};
  26. #case {"ESCAPE/["} {#local state {INNER}};
  27. #case {"ESCAPE/%*"} {#local state {OUTER}};
  28. #case {"INNER/m"} {#cat str {;}; #local state {OUTER}};
  29. #case {"INNER/%*"} {#cat str {$ch}};
  30. #case {"OUTER/\e"} {#local state {ESCAPE}};
  31. };
  32. };
  33. #replace {str} {;$} {};
  34. #return {$str};
  35. };
  36. ///=== {
  37. // #@ util.WordColor <字符串> <子字符串>
  38. // 从字符串中检索子字符串,如果找到它,就把它的颜色取出来并作为返回值,否则返回空。
  39. // };
  40. #func {str.WordColor} {
  41. #local str {%1};
  42. #local word {%2};
  43. #replace word {%+1u} {(?:\e\[[0-9;:]*m)*&1};
  44. #replace str {^%*{$word}%*$} {&2};
  45. #local color {@str.Color{$str}};
  46. #replace color {^0;} {};
  47. #replace color {;0$} {};
  48. #replace color {2;37;0;} {};
  49. #replace color {;2;37;0} {};
  50. #return {$color};
  51. };
  52. ///=== {
  53. // #@ util.ColorBar <字符串> <颜色1> <权重1> <颜色2> <权重2> [...]
  54. // 将字符串按照颜色及其对应的权重占比,渲染成彩色字符串。注意颜色参数须按顺序排列。
  55. // };
  56. #func {util.ColorBar} {
  57. #local str {%1};
  58. #local args {};
  59. #list args create {%0};
  60. #list args delete {1} {1};
  61. #local parts {};
  62. #list parts create {};
  63. #local count {0};
  64. #local sum {0};
  65. #while { $count < &args[] } {
  66. #local color {$args[@math.Eval{$count + 1}]};
  67. #local weight {@defaultNum{$args[@math.Eval{$count + 2}];0}};
  68. #list parts {add} {{
  69. {color}{$color}
  70. {weight}{$weight}
  71. }};
  72. #math count {$count + 2};
  73. #math sum {$sum + $weight};
  74. };
  75. #local elem {};
  76. #local len {@str.Len{$str}};
  77. #local leftLen {0};
  78. #local leftWeight {0};
  79. #local colorStr {};
  80. #foreach {$parts[%*]} {elem} {
  81. #local elemLen {@math.Eval{($elem[weight] + $leftWeight) * $len / $sum - $leftLen}};
  82. #local text {@str.Sub{{$str};$leftLen;$elemLen}};
  83. #cat colorStr {$elem[color]$text<299>};
  84. #math leftLen {$leftLen + $elemLen};
  85. #math leftWeight {$leftWeight + $elem[weight]};
  86. };
  87. #return {$colorStr};
  88. };
  89. ///=== {
  90. // #@ raw.ParseFragments <颜色字符串> [<之前的颜色>]
  91. // 解析包含颜色代码的字符串,将其拆分成一系列的文本片段,每个片段包含一段文本和对应的颜色属性。
  92. // 本函数实际上有两个返回值,以 table 形式打包返回,其中包含以下内容:
  93. // - color: 字符串解析完毕之后,最后的颜色状态(可供之后调用),参见下面的 color 属性。
  94. // - fragments: 一个表格列表,其中每个元素是一个表格,包含以下两个字段:
  95. // - text: 文本片段
  96. // - color:
  97. // - fg: 前景色,可能是一个一位数(16色系统)或一个多位数(256色系统)或三个数(RGB颜色)
  98. // - bg: 背景色,同上
  99. // - attrs: 文本样式属性,是一个字符串集合(sset),包含以下可能的值:
  100. // - bold
  101. // - italic
  102. // - underline
  103. // - blink
  104. // - reverse
  105. // - dim
  106. // - f256 表示前景色是 256 色系统
  107. // - b256 表示背景色是 256 色系统
  108. // - ftrue 表示前景色是真彩色
  109. // - btrue 表示背景色是真彩色
  110. // };
  111. #func {raw.ParseFragments} {
  112. #local str {%1};
  113. #local color {%2};
  114. #local fragments {};
  115. #local fragment {};
  116. #local tokens {};
  117. #local mode {TEXT};
  118. #while {1} {
  119. #if { &tokens[] == 0 } {
  120. raw.ParseFragments.nextToken;
  121. #if { "$str" === "" } {
  122. #break;
  123. };
  124. #if { "$str[text]" !== "" } {
  125. #local fragment[text] {$str[text]};
  126. #local fragment[color] {$color};
  127. #list fragments add {{$fragment}};
  128. #local fragment {};
  129. };
  130. #elseif { "$str[color]" !== "" } {
  131. #replace str[color] {^\e[} {};
  132. #replace str[color] {m$} {};
  133. #if { "$str[color]" !== "" } {
  134. #list tokens add {$str[color]};
  135. #local mode {SGR};
  136. };
  137. };
  138. #local str {$str[rest]};
  139. #continue;
  140. };
  141. #local token {$tokens[1]};
  142. #list tokens delete 1;
  143. #switch {"$mode/$token"} {
  144. #case {"SGR/0"} {#local color[fg] {0}; #local color[bg] {0}; #local color[attrs] {}};
  145. #case {"SGR/1"} {#local color[attrs] {@sset.Add{{$color[attrs]};bold}}};
  146. #case {"SGR/2"} {#local color[attrs] {@sset.Add{{$color[attrs]};dim}}};
  147. #case {"SGR/3"} {#local color[attrs] {@sset.Add{{$color[attrs]};italic}}};
  148. #case {"SGR/4"} {#local color[attrs] {@sset.Add{{$color[attrs]};underline}}};
  149. #case {"SGR/5"} {#local color[attrs] {@sset.Add{{$color[attrs]};blink}}};
  150. #case {"SGR/7"} {#local color[attrs] {@sset.Add{{$color[attrs]};reverse}}};
  151. #case {"SGR/3{[0-7]}"} {#local color[fg] {$token}};
  152. #case {"SGR/4{[0-7]}"} {#local color[bg] {$token}};
  153. #case {"SGR/9{[0-7]}"} {#local color[fg] {$token}};
  154. #case {"SGR/10{[0-7]}"} {#local color[bg] {$token}};
  155. #case {"SGR/38"} {#local mode {SGR38}};
  156. #case {"SGR38/2"} {#local mode {SGR382}};
  157. #case {"SGR382/%+1..d"} {#local mode {SGR}; #local color[fg] {$token}; #local color[attrs] {@sset.Add{{$color[attrs]};f256}}};
  158. #case {"SGR38/5"} {#local mode {SGR385}; #local color[fg] {}; #local color[attrs] {@sset.Add{{$color[attrs]};ftrue}}};
  159. #case {"SGR385/%+1..d"} {#local mode {SGR3851}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  160. #case {"SGR3851/%+1..d"} {#local mode {SGR3852}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  161. #case {"SGR3852/%+1..d"} {#local mode {SGR}; #local color[fg] {@slist.Append{{$color[fg]};$token}}};
  162. #case {"SGR/48"} {#local mode {SGR48}};
  163. #case {"SGR48/2"} {#local mode {SGR482}};
  164. #case {"SGR482/%+1..d"} {#local mode {SGR}; #local color[bg] {$token}; #local color[attrs] {@sset.Add{{$color[attrs]};b256}}};
  165. #case {"SGR48/5"} {#local mode {SGR485}; #local color[bg] {}; #local color[attrs] {@sset.Add{{$color[attrs]};btrue}}};
  166. #case {"SGR485/%+1..d"} {#local mode {SGR4851}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  167. #case {"SGR4851/%+1..d"} {#local mode {SGR4852}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  168. #case {"SGR4852/%+1..d"} {#local mode {SGR}; #local color[bg] {@slist.Append{{$color[bg]};$token}}};
  169. };
  170. };
  171. #if { "$str[text]" !== "" } {
  172. #local fragment[text] {$str[text]};
  173. #local fragment[color] {$color};
  174. #list fragments add {{$fragment}};
  175. };
  176. #return {
  177. {fragments} {$fragments}
  178. {color} {$color}
  179. };
  180. };
  181. #alias {raw.ParseFragments.nextToken} {
  182. #if { "$str" === "" } {
  183. #return;
  184. };
  185. #replace str {^{[^\e]+}%*$} {
  186. {text} {&1}
  187. {rest} {&2}
  188. };
  189. #var str {$str};
  190. #if { "$str[text]" !== "" } {
  191. #return {$str};
  192. };
  193. #replace str {^%+1c%*$} {
  194. {color} {&1}
  195. {rest} {&2}
  196. };
  197. #var str {$str};
  198. #if { "$str[text]" !== "" } {
  199. #return {$str};
  200. };
  201. #replace str {^\e%*} {
  202. {esc} {&1}
  203. {rest} {&2}
  204. };
  205. #var str {$str};
  206. };
  207. ///=== {
  208. // #@ raw.EraseInvisible <颜色字符串> [<是否压缩>]
  209. // 解析包含颜色代码的字符串,将其拆分成多个片段,并擦掉其中前景色与背景色相同的部分。
  210. // 如果同时指定了第二个参数为真,则将颜色相同的连续多个部分进行合并。
  211. // 最终返回值格式与 raw.ParseFragments 相同。
  212. // };
  213. #func {raw.EraseInvisible} {
  214. #local str {%1};
  215. #local zip {%2};
  216. #local color {};
  217. #local output {};
  218. #local str {@raw.ParseFragments{{$str}}};
  219. #foreach {*str[fragments][]} {fragment} {
  220. #local fragment {$str[fragments][$fragment]};
  221. #if { ! @sset.Contains{{$fragment[color][attrs]};f256}
  222. && ! @sset.Contains{{$fragment[color][attrs]};b256}
  223. && ! @sset.Contains{{$fragment[color][attrs]};ftrue}
  224. && ! @sset.Contains{{$fragment[color][attrs]};btrue}
  225. && $fragment[color][fg] + 10 == $fragment[color][bg] } {
  226. #continue;
  227. };
  228. #if { @sset.Contains{{$fragment[color][attrs]};f256}
  229. && @sset.Contains{{$fragment[color][attrs]};b256}
  230. && "$fragment[color][fg]" === "$fragment[color][bg]" } {
  231. #continue;
  232. };
  233. #if { @sset.Contains{{$fragment[color][attrs]};ftrue}
  234. && @sset.Contains{{$fragment[color][attrs]};btrue}
  235. && "$fragment[color][fg]" === "$fragment[color][bg]" } {
  236. #continue;
  237. };
  238. #if { @isTrue{$zip} && "$color" != "" && "$fragment[color]" === "$color" } {
  239. #cat output[-1][text] {$fragment[text]};
  240. };
  241. #else {
  242. #list output add {{$fragment}};
  243. #local color {$fragment[color]};
  244. };
  245. };
  246. #return {
  247. {fragments} {$output}
  248. {color} {$color}
  249. };
  250. };