Kitty Graphics Protocol in OCaml
terminal graphics ocaml
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; 0x030D; 0x030E; 0x0310; 0x0312; 0x033D; 0x033E; 0x033F; 13 0x0346; 0x034A; 0x034B; 0x034C; 0x0350; 0x0351; 0x0352; 0x0357; 14 0x035B; 0x0363; 0x0364; 0x0365; 0x0366; 0x0367; 0x0368; 0x0369; 15 0x036A; 0x036B; 0x036C; 0x036D; 0x036E; 0x036F; 0x0483; 0x0484; 16 0x0485; 0x0486; 0x0487; 0x0592; 0x0593; 0x0594; 0x0595; 0x0597; 17 0x0598; 0x0599; 0x059C; 0x059D; 0x059E; 0x059F; 0x05A0; 0x05A1; 18 0x05A8; 0x05A9; 0x05AB; 0x05AC; 0x05AF; 0x05C4; 0x0610; 0x0611; 19 0x0612; 0x0613; 0x0614; 0x0615; 0x0616; 0x0617; 0x0657; 0x0658; 20 0x0659; 0x065A; 0x065B; 0x065D; 0x065E; 0x06D6; 0x06D7; 0x06D8; 21 0x06D9; 0x06DA; 0x06DB; 0x06DC; 0x06DF; 0x06E0; 0x06E1; 0x06E2; 22 0x06E4; 0x06E7; 0x06E8; 0x06EB; 0x06EC; 0x0730; 0x0732; 0x0733; 23 0x0735; 0x0736; 0x073A; 0x073D; 0x073F; 0x0740; 0x0741; 0x0743; 24 0x0745; 0x0747; 0x0749; 0x074A; 0x07EB; 0x07EC; 0x07ED; 0x07EE; 25 0x07EF; 0x07F0; 0x07F1; 0x07F3; 0x0816; 0x0817; 0x0818; 0x0819; 26 0x081B; 0x081C; 0x081D; 0x081E; 0x081F; 0x0820; 0x0821; 0x0822; 27 0x0823; 0x0825; 0x0826; 0x0827; 0x0829; 0x082A; 0x082B; 0x082C; 28 0x082D; 0x0951; 0x0953; 0x0954; 0x0F82; 0x0F83; 0x0F86; 0x0F87; 29 0x135D; 0x135E; 0x135F; 0x17DD; 0x193A; 0x1A17; 0x1A75; 0x1A76; 30 0x1A77; 0x1A78; 0x1A79; 0x1A7A; 0x1A7B; 0x1A7C; 0x1B6B; 0x1B6D; 31 0x1B6E; 0x1B6F; 0x1B70; 0x1B71; 0x1B72; 0x1B73; 0x1CD0; 0x1CD1; 32 0x1CD2; 0x1CDA; 0x1CDB; 0x1CE0; 0x1DC0; 0x1DC1; 0x1DC3; 0x1DC4; 33 0x1DC5; 0x1DC6; 0x1DC7; 0x1DC8; 0x1DC9; 0x1DCB; 0x1DCC; 0x1DD1; 34 0x1DD2; 0x1DD3; 0x1DD4; 0x1DD5; 0x1DD6; 0x1DD7; 0x1DD8; 0x1DD9; 35 0x1DDA; 0x1DDB; 0x1DDC; 0x1DDD; 0x1DDE; 0x1DDF; 0x1DE0; 0x1DE1; 36 0x1DE2; 0x1DE3; 0x1DE4; 0x1DE5; 0x1DE6; 0x1DFE; 0x20D0; 0x20D1; 37 0x20D4; 0x20D5; 0x20D6; 0x20D7; 0x20DB; 0x20DC; 0x20E1; 0x20E7; 38 0x20E9; 0x20F0; 0xA66F; 0xA67C; 0xA67D; 0xA6F0; 0xA6F1; 0xA8E0; 39 0xA8E1; 0xA8E2; 0xA8E3; 0xA8E4; 0xA8E5; 0xA8E6; 0xA8E7; 0xA8E8; 40 0xA8E9; 0xA8EA; 0xA8EB; 0xA8EC; 0xA8ED; 0xA8EE; 0xA8EF; 0xA8F0; 41 0xA8F1; 0xAAB0; 0xAAB2; 0xAAB3; 0xAAB7; 0xAAB8; 0xAABE; 0xAABF; 42 0xAAC1; 0xFE20; 0xFE21; 0xFE22; 0xFE23; 0xFE24; 0xFE25; 0xFE26; 43 0x10A0F; 0x10A38; 0x1D185; 0x1D186; 0x1D187; 0x1D188; 0x1D189; 44 0x1D1AA; 0x1D1AB; 0x1D1AC; 0x1D1AD; 0x1D242; 0x1D243; 0x1D244; 45 |] 46 47let diacritic n = Uchar.of_int diacritics.(n mod Array.length diacritics) 48let row_diacritic = diacritic 49let column_diacritic = diacritic 50let id_high_byte_diacritic = diacritic 51 52let next_image_id () = 53 let rec gen () = 54 let id = Random.int32 Int32.max_int |> Int32.to_int in 55 (* Ensure high byte and middle bytes are non-zero *) 56 if id land 0xFF000000 = 0 || id land 0x00FFFF00 = 0 then gen () 57 else id 58 in 59 gen () 60 61let add_uchar buf u = 62 let code = Uchar.to_int u in 63 let put = Buffer.add_char buf in 64 if code < 0x80 then put (Char.chr code) 65 else if code < 0x800 then ( 66 put (Char.chr (0xC0 lor (code lsr 6))); 67 put (Char.chr (0x80 lor (code land 0x3F)))) 68 else if code < 0x10000 then ( 69 put (Char.chr (0xE0 lor (code lsr 12))); 70 put (Char.chr (0x80 lor ((code lsr 6) land 0x3F))); 71 put (Char.chr (0x80 lor (code land 0x3F)))) 72 else ( 73 put (Char.chr (0xF0 lor (code lsr 18))); 74 put (Char.chr (0x80 lor ((code lsr 12) land 0x3F))); 75 put (Char.chr (0x80 lor ((code lsr 6) land 0x3F))); 76 put (Char.chr (0x80 lor (code land 0x3F)))) 77 78let write buf ~image_id ?placement_id ~rows ~cols () = 79 (* Set foreground color using colon subparameter format *) 80 Printf.bprintf buf "\027[38:2:%d:%d:%dm" 81 ((image_id lsr 16) land 0xFF) 82 ((image_id lsr 8) land 0xFF) 83 (image_id land 0xFF); 84 (* Optional placement ID in underline color *) 85 placement_id 86 |> Option.iter (fun pid -> 87 Printf.bprintf buf "\027[58:2:%d:%d:%dm" 88 ((pid lsr 16) land 0xFF) 89 ((pid lsr 8) land 0xFF) 90 (pid land 0xFF)); 91 (* High byte diacritic - always written, even when 0 *) 92 let high_byte = (image_id lsr 24) land 0xFF in 93 let id_diac = id_high_byte_diacritic high_byte in 94 (* Write grid *) 95 for row = 0 to rows - 1 do 96 for col = 0 to cols - 1 do 97 add_uchar buf placeholder_char; 98 add_uchar buf (row_diacritic row); 99 add_uchar buf (column_diacritic col); 100 add_uchar buf id_diac 101 done; 102 if row < rows - 1 then Buffer.add_string buf "\n\r" 103 done; 104 (* Reset colors *) 105 Buffer.add_string buf "\027[39m"; 106 if Option.is_some placement_id then Buffer.add_string buf "\027[59m"