Kitty Graphics Protocol in OCaml
terminal graphics ocaml
at main 5.7 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(* Kitty Graphics Protocol Unicode Placeholders - Implementation *) 7 8let placeholder_char = Uchar.of_int 0x10EEEE 9 10let diacritics = 11 [| 12 0x0305; 13 0x030D; 14 0x030E; 15 0x0310; 16 0x0312; 17 0x033D; 18 0x033E; 19 0x033F; 20 0x0346; 21 0x034A; 22 0x034B; 23 0x034C; 24 0x0350; 25 0x0351; 26 0x0352; 27 0x0357; 28 0x035B; 29 0x0363; 30 0x0364; 31 0x0365; 32 0x0366; 33 0x0367; 34 0x0368; 35 0x0369; 36 0x036A; 37 0x036B; 38 0x036C; 39 0x036D; 40 0x036E; 41 0x036F; 42 0x0483; 43 0x0484; 44 0x0485; 45 0x0486; 46 0x0487; 47 0x0592; 48 0x0593; 49 0x0594; 50 0x0595; 51 0x0597; 52 0x0598; 53 0x0599; 54 0x059C; 55 0x059D; 56 0x059E; 57 0x059F; 58 0x05A0; 59 0x05A1; 60 0x05A8; 61 0x05A9; 62 0x05AB; 63 0x05AC; 64 0x05AF; 65 0x05C4; 66 0x0610; 67 0x0611; 68 0x0612; 69 0x0613; 70 0x0614; 71 0x0615; 72 0x0616; 73 0x0617; 74 0x0657; 75 0x0658; 76 0x0659; 77 0x065A; 78 0x065B; 79 0x065D; 80 0x065E; 81 0x06D6; 82 0x06D7; 83 0x06D8; 84 0x06D9; 85 0x06DA; 86 0x06DB; 87 0x06DC; 88 0x06DF; 89 0x06E0; 90 0x06E1; 91 0x06E2; 92 0x06E4; 93 0x06E7; 94 0x06E8; 95 0x06EB; 96 0x06EC; 97 0x0730; 98 0x0732; 99 0x0733; 100 0x0735; 101 0x0736; 102 0x073A; 103 0x073D; 104 0x073F; 105 0x0740; 106 0x0741; 107 0x0743; 108 0x0745; 109 0x0747; 110 0x0749; 111 0x074A; 112 0x07EB; 113 0x07EC; 114 0x07ED; 115 0x07EE; 116 0x07EF; 117 0x07F0; 118 0x07F1; 119 0x07F3; 120 0x0816; 121 0x0817; 122 0x0818; 123 0x0819; 124 0x081B; 125 0x081C; 126 0x081D; 127 0x081E; 128 0x081F; 129 0x0820; 130 0x0821; 131 0x0822; 132 0x0823; 133 0x0825; 134 0x0826; 135 0x0827; 136 0x0829; 137 0x082A; 138 0x082B; 139 0x082C; 140 0x082D; 141 0x0951; 142 0x0953; 143 0x0954; 144 0x0F82; 145 0x0F83; 146 0x0F86; 147 0x0F87; 148 0x135D; 149 0x135E; 150 0x135F; 151 0x17DD; 152 0x193A; 153 0x1A17; 154 0x1A75; 155 0x1A76; 156 0x1A77; 157 0x1A78; 158 0x1A79; 159 0x1A7A; 160 0x1A7B; 161 0x1A7C; 162 0x1B6B; 163 0x1B6D; 164 0x1B6E; 165 0x1B6F; 166 0x1B70; 167 0x1B71; 168 0x1B72; 169 0x1B73; 170 0x1CD0; 171 0x1CD1; 172 0x1CD2; 173 0x1CDA; 174 0x1CDB; 175 0x1CE0; 176 0x1DC0; 177 0x1DC1; 178 0x1DC3; 179 0x1DC4; 180 0x1DC5; 181 0x1DC6; 182 0x1DC7; 183 0x1DC8; 184 0x1DC9; 185 0x1DCB; 186 0x1DCC; 187 0x1DD1; 188 0x1DD2; 189 0x1DD3; 190 0x1DD4; 191 0x1DD5; 192 0x1DD6; 193 0x1DD7; 194 0x1DD8; 195 0x1DD9; 196 0x1DDA; 197 0x1DDB; 198 0x1DDC; 199 0x1DDD; 200 0x1DDE; 201 0x1DDF; 202 0x1DE0; 203 0x1DE1; 204 0x1DE2; 205 0x1DE3; 206 0x1DE4; 207 0x1DE5; 208 0x1DE6; 209 0x1DFE; 210 0x20D0; 211 0x20D1; 212 0x20D4; 213 0x20D5; 214 0x20D6; 215 0x20D7; 216 0x20DB; 217 0x20DC; 218 0x20E1; 219 0x20E7; 220 0x20E9; 221 0x20F0; 222 0xA66F; 223 0xA67C; 224 0xA67D; 225 0xA6F0; 226 0xA6F1; 227 0xA8E0; 228 0xA8E1; 229 0xA8E2; 230 0xA8E3; 231 0xA8E4; 232 0xA8E5; 233 0xA8E6; 234 0xA8E7; 235 0xA8E8; 236 0xA8E9; 237 0xA8EA; 238 0xA8EB; 239 0xA8EC; 240 0xA8ED; 241 0xA8EE; 242 0xA8EF; 243 0xA8F0; 244 0xA8F1; 245 0xAAB0; 246 0xAAB2; 247 0xAAB3; 248 0xAAB7; 249 0xAAB8; 250 0xAABE; 251 0xAABF; 252 0xAAC1; 253 0xFE20; 254 0xFE21; 255 0xFE22; 256 0xFE23; 257 0xFE24; 258 0xFE25; 259 0xFE26; 260 0x10A0F; 261 0x10A38; 262 0x1D185; 263 0x1D186; 264 0x1D187; 265 0x1D188; 266 0x1D189; 267 0x1D1AA; 268 0x1D1AB; 269 0x1D1AC; 270 0x1D1AD; 271 0x1D242; 272 0x1D243; 273 0x1D244; 274 |] 275 276let diacritic n = Uchar.of_int diacritics.(n mod Array.length diacritics) 277let row_diacritic = diacritic 278let column_diacritic = diacritic 279let id_high_byte_diacritic = diacritic 280 281let next_image_id () = 282 let rec gen () = 283 let id = Random.int32 Int32.max_int |> Int32.to_int in 284 (* Ensure high byte and middle bytes are non-zero *) 285 if id land 0xFF000000 = 0 || id land 0x00FFFF00 = 0 then gen () else id 286 in 287 gen () 288 289let add_uchar buf u = 290 let code = Uchar.to_int u in 291 let put = Buffer.add_char buf in 292 if code < 0x80 then put (Char.chr code) 293 else if code < 0x800 then ( 294 put (Char.chr (0xC0 lor (code lsr 6))); 295 put (Char.chr (0x80 lor (code land 0x3F)))) 296 else if code < 0x10000 then ( 297 put (Char.chr (0xE0 lor (code lsr 12))); 298 put (Char.chr (0x80 lor ((code lsr 6) land 0x3F))); 299 put (Char.chr (0x80 lor (code land 0x3F)))) 300 else ( 301 put (Char.chr (0xF0 lor (code lsr 18))); 302 put (Char.chr (0x80 lor ((code lsr 12) land 0x3F))); 303 put (Char.chr (0x80 lor ((code lsr 6) land 0x3F))); 304 put (Char.chr (0x80 lor (code land 0x3F)))) 305 306let write buf ~image_id ?placement_id ~rows ~cols () = 307 (* Set foreground color using colon subparameter format *) 308 Printf.bprintf buf "\027[38:2:%d:%d:%dm" 309 ((image_id lsr 16) land 0xFF) 310 ((image_id lsr 8) land 0xFF) 311 (image_id land 0xFF); 312 (* Optional placement ID in underline color *) 313 placement_id 314 |> Option.iter (fun pid -> 315 Printf.bprintf buf "\027[58:2:%d:%d:%dm" 316 ((pid lsr 16) land 0xFF) 317 ((pid lsr 8) land 0xFF) 318 (pid land 0xFF)); 319 (* High byte diacritic - always written, even when 0 *) 320 let high_byte = (image_id lsr 24) land 0xFF in 321 let id_diac = id_high_byte_diacritic high_byte in 322 (* Write grid *) 323 for row = 0 to rows - 1 do 324 for col = 0 to cols - 1 do 325 add_uchar buf placeholder_char; 326 add_uchar buf (row_diacritic row); 327 add_uchar buf (column_diacritic col); 328 add_uchar buf id_diac 329 done; 330 if row < rows - 1 then Buffer.add_string buf "\n\r" 331 done; 332 (* Reset colors *) 333 Buffer.add_string buf "\027[39m"; 334 if Option.is_some placement_id then Buffer.add_string buf "\027[59m"