this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

sync tgp

+843 -126
+113 -46
tgp/README.md
··· 1 - # Textsize 1 + # Termext - Terminal Extensions for OCaml 2 2 3 - A clean, standalone OCaml library implementing the [Kitty text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol/). 3 + A clean OCaml library implementing Kitty terminal protocols for enhanced terminal output. 4 4 5 5 ## Overview 6 6 7 - The Kitty text sizing protocol (introduced in kitty v0.40.0) allows terminals to render text in different sizes, enabling typographic features like headlines, superscripts, and subscripts. 7 + Termext provides OCaml bindings for modern terminal protocols: 8 + 9 + - **Text Sizing**: The [Kitty text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol/) for rendering text at different sizes 10 + - **Graphics**: The [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) for displaying images and animations 8 11 9 12 ## Features 10 13 14 + ### Text Sizing (Textsize module) 11 15 - ✨ Clean, type-safe API with validation 12 - - 📦 Zero dependencies (besides OCaml standard library) 13 16 - 🎯 Full protocol support (scale, width, fractional sizing, alignment) 14 - - 🚀 Convenient helper functions for common use cases 15 - - 📝 Comprehensive documentation 17 + - 🚀 Convenient helper functions (double, triple, superscript, subscript) 18 + - 📐 Fmt-style formatters 19 + 20 + ### Graphics (Termgraph module - In Development) 21 + - 🖼️ PNG image display 22 + - 📁 File and direct transmission 23 + - 🎬 Animation support 24 + - 🎨 Z-index layering 25 + - 📦 Fmt-style formatters 26 + - 🎯 Type-safe placement control 16 27 17 28 ## Installation 18 29 19 30 ```bash 20 31 # Via opam (once published) 21 - opam install textsize 32 + opam install termext 22 33 23 34 # Or add to your dune-project dependencies 24 35 (depends 25 - (textsize (>= 0.1.0))) 36 + (termext (>= 0.1.0))) 26 37 ``` 27 38 28 39 ## Quick Start 29 40 41 + ### Text Sizing 42 + 30 43 ```ocaml 31 44 (* Simple convenience functions *) 32 - print_string (Textsize.double "Hello, World!");; 33 - print_string (Textsize.triple "Big Text");; 34 - print_string (Textsize.half "Small text");; 45 + print_string (Termext.Textsize.double "Hello, World!");; 46 + print_string (Termext.Textsize.triple "Big Text");; 47 + print_string (Termext.Textsize.half "Small text");; 35 48 36 49 (* Superscripts and subscripts *) 37 50 print_string "E=mc"; 38 - print_string (Textsize.superscript "2");; 51 + print_string (Termext.Textsize.superscript "2");; 39 52 40 53 print_string "H"; 41 - print_string (Textsize.subscript "2"); 54 + print_string (Termext.Textsize.subscript "2"); 42 55 print_string "O";; 43 56 44 - (* Custom sizing with metadata *) 45 - let custom = 46 - Textsize.empty 47 - |> Textsize.with_scale (Textsize.make_scale 4) 48 - |> Textsize.with_width (Textsize.make_width 6) 57 + (* Fmt integration *) 58 + Fmt.pr "The answer is %a\n" 59 + (Termext.Textsize.styled_double Fmt.int) 42;; 60 + ``` 61 + 62 + ### Graphics (Preview) 63 + 64 + ```ocaml 65 + (* Display a PNG file *) 66 + print_string (Termext.Termgraph.display_png_file "image.png");; 67 + 68 + (* With size control *) 69 + print_string (Termext.Termgraph.display_png_file 70 + ~rows:10 71 + ~columns:20 72 + "logo.png");; 73 + 74 + (* Fmt integration *) 75 + let img = Termext.Termgraph.v 76 + ~rows:15 77 + (Termext.Termgraph.File { path = "banner.png" }) 78 + () 49 79 in 50 - print_string (Textsize.render custom "Custom sized");; 80 + Fmt.pr "Welcome! %a\n" Termext.Termgraph.pp img;; 81 + 82 + (* Animation *) 83 + let anim_id = Termext.Termgraph.image_id_of_string "spinner" in 84 + let frame1 = Termext.Termgraph.Animation.frame 85 + ~gap:100 86 + (Termext.Termgraph.File { path = "frame1.png" }) 87 + 1 88 + in 89 + print_string (Termext.Termgraph.Animation.render_frame anim_id frame1);; 51 90 ``` 52 91 53 92 ## API Overview 54 93 55 - ### Core Types 94 + ### Textsize Module 56 95 57 - - `scale` - Text scale factor (1-7) 58 - - `width` - Width in cells (0-7) 59 - - `numerator`/`denominator` - Fractional scaling (0-15) 60 - - `vertical_align` - Vertical alignment (Bottom, Center, Top) 61 - - `horizontal_align` - Horizontal alignment (Left, Center, Right) 62 - - `metadata` - Combined sizing metadata 63 - 64 - ### Convenience Functions 96 + **Types & Builders** 97 + - `vertical`, `horizontal` - Alignment types 98 + - `v ?scale ?width ?fraction ?vertical ?horizontal ()` - Create sizing metadata 99 + - `empty` - Empty metadata 65 100 101 + **Convenience Functions** 66 102 - `double`, `triple`, `quadruple` - Quick size multipliers 67 103 - `half` - Half-sized text 68 104 - `superscript`, `subscript` - Typographic positioning 69 105 - `scaled n` - Arbitrary scale factor 70 106 71 - ### Metadata Builders 107 + **Rendering** 108 + - `render metadata text` - Generate escape sequence 109 + - `render_to_channel oc metadata text` - Write to channel 110 + - `pp`, `styled` - Fmt formatters 72 111 73 - - `empty` - Start with empty metadata 74 - - `with_scale` - Set scale factor 75 - - `with_width` - Set width 76 - - `with_fraction` - Set fractional scaling 77 - - `with_vertical_align` - Set vertical alignment 78 - - `with_horizontal_align` - Set horizontal alignment 112 + ### Termgraph Module (In Development) 113 + 114 + **Core Types** 115 + - `format` - RGB, RGBA, PNG 116 + - `compression` - No_compression, Zlib 117 + - `transmission` - Direct, File 118 + - `image_id`, `placement_id` - Identifiers 119 + 120 + **Placement** 121 + - `v ?image_id ?x ?y ?width ?height ?rows ?columns ?z_index transmission ()` - Create placement 122 + 123 + **Animation** 124 + - `Animation.frame` - Define animation frames 125 + - `Animation.render_frame` - Upload frames 126 + - `Animation.render_control` - Control playback 127 + 128 + **Deletion** 129 + - `Delete.render` - Delete images by various criteria 79 130 80 - ### Rendering 131 + **Convenience** 132 + - `display_png_file` - Quick PNG display 133 + - `display_png_bytes` - Display PNG from bytes 134 + - `pp`, `pp_delete`, `pp_animation_*` - Fmt formatters 81 135 82 - - `render metadata text` - Generate escape sequence 83 - - `render_to_channel oc metadata text` - Write to channel 136 + See [TODO.md](TODO.md) for implementation status and roadmap. 84 137 85 138 ## Building from Source 86 139 87 140 ```bash 88 - cd textsize 141 + cd termext 89 142 dune build 90 - dune exec example/demo.exe # Run example 143 + dune exec example/demo.exe # Text sizing demo 144 + dune exec example/graphics_demo.exe # Graphics demo (requires PNG files) 91 145 ``` 92 146 93 147 ## Terminal Compatibility 94 148 95 - This library generates escape sequences for the Kitty text sizing protocol. It requires a terminal emulator that supports this protocol: 149 + This library generates escape sequences for Kitty terminal protocols. 96 150 151 + **Text Sizing Protocol:** 97 152 - ✅ [Kitty](https://sw.kovidgoyal.net/kitty/) (v0.40.0+) 98 - - ✅ [Ghostty](https://ghostty.org/) (with protocol support) 153 + - ✅ [Ghostty](https://ghostty.org/) 154 + 155 + **Graphics Protocol:** 156 + - ✅ [Kitty](https://sw.kovidgoyal.net/kitty/) 157 + - ✅ [Ghostty](https://ghostty.org/) 158 + - ⚠️ [WezTerm](https://wezfurlong.org/wezterm/) (partial support) 159 + - ⚠️ [Konsole](https://konsole.kde.org/) (partial support) 99 160 100 - Other terminals will typically ignore these sequences gracefully. 161 + Other terminals will typically ignore these sequences gracefully (no errors, just no special rendering). 101 162 102 163 ## Protocol Reference 103 164 104 - The library implements OSC 66 sequences as specified in the [Kitty text sizing protocol documentation](https://sw.kovidgoyal.net/kitty/text-sizing-protocol/). 165 + **Text Sizing:** OSC 66 sequences - `ESC ] 66 ; metadata ; text BEL` 166 + - See: [Kitty Text Sizing Protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol/) 167 + 168 + **Graphics:** APC sequences - `ESC _G<control>;<payload>ESC \` 169 + - See: [Kitty Graphics Protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) 105 170 106 - Format: `ESC ] 66 ; metadata ; text BEL` 171 + ## Dependencies 107 172 108 - Where metadata is colon-separated key=value pairs. 173 + - **fmt**: Formatting library (already widely used) 174 + - **base64**: Base64 encoding for graphics transmission 175 + - **camlzip**: Zlib compression for graphics (optional optimization) 109 176 110 177 ## License 111 178
+252
tgp/TODO.md
··· 1 + # TODO - Termext Library Development 2 + 3 + ## Current Status 4 + 5 + ### Completed 6 + - ✅ Text sizing protocol (Textsize module) 7 + - ✅ Graphics protocol interface design (Termgraph module) 8 + - ✅ Library renamed from `textsize` to `termext` 9 + - ✅ Dependencies added (base64, camlzip) 10 + 11 + ### In Progress 12 + - 🚧 Termgraph module implementation 13 + - 🚧 Animation support 14 + - 🚧 Example binaries 15 + 16 + ## Graphics Protocol Implementation Plan 17 + 18 + ### Phase 1: Core Transmission (Current) 19 + 20 + **Direct Transmission** 21 + - [x] Interface design 22 + - [ ] Base64 encoding of image data 23 + - [ ] Chunking for large images (4096 byte chunks) 24 + - [ ] PNG format support (user-provided bytes) 25 + - [ ] RGB/RGBA raw format support 26 + - [ ] Escape sequence generation (APC format) 27 + 28 + **File Transmission** 29 + - [x] Interface design 30 + - [ ] File path validation 31 + - [ ] Escape sequence generation for file paths 32 + - [ ] Handle file transmission medium (t=f) 33 + 34 + **Placement Control** 35 + - [x] Interface design 36 + - [ ] Position control (x, y offsets) 37 + - [ ] Size control (width, height, rows, columns) 38 + - [ ] Z-index support 39 + - [ ] Cursor movement control 40 + - [ ] Image and placement IDs 41 + 42 + **Deletion** 43 + - [x] Interface design 44 + - [ ] Delete by image ID 45 + - [ ] Delete by placement ID 46 + - [ ] Delete at cursor 47 + - [ ] Delete all images 48 + 49 + ### Phase 2: Animation Support 50 + 51 + **Frame Transmission** 52 + - [x] Interface design 53 + - [ ] Frame numbering (1-based) 54 + - [ ] Composition modes (blend, overwrite) 55 + - [ ] Frame gap timing (milliseconds) 56 + - [ ] Multi-frame upload 57 + 58 + **Animation Control** 59 + - [x] Interface design 60 + - [ ] Set gap for specific frames 61 + - [ ] Loop control (count, infinite) 62 + - [ ] Start/stop animation 63 + - [ ] Frame composition control 64 + 65 + ### Phase 3: Advanced Features (Future) 66 + 67 + **Compression** 68 + - [ ] ZLIB compression for image data 69 + - [ ] Automatic compression for large images 70 + - [ ] Compression level control 71 + 72 + **Temporary File Transmission** 73 + - [ ] Temp file creation 74 + - [ ] Escape sequence generation (t=t) 75 + - [ ] Cleanup handling 76 + 77 + **Shared Memory Transmission** 78 + - [ ] Shared memory name generation 79 + - [ ] Escape sequence generation (t=s) 80 + - [ ] Platform-specific support (POSIX shared memory) 81 + 82 + **Virtual Placements** 83 + - [ ] Unicode placeholder support 84 + - [ ] Virtual placement references 85 + - [ ] Diacritic composition 86 + 87 + **Query/Response Handling** 88 + - [ ] Terminal capability detection 89 + - [ ] Success/failure response parsing 90 + - [ ] Error handling 91 + 92 + **Advanced Placement Options** 93 + - [ ] Source rectangle (crop images) 94 + - [ ] Cell offset positioning 95 + - [ ] Relative positioning 96 + - [ ] Background/foreground placement 97 + 98 + **Image Composition** 99 + - [ ] Alpha blending modes 100 + - [ ] Composition operations 101 + - [ ] Multi-layer support 102 + 103 + ### Phase 4: Eio Integration (Future) 104 + 105 + **Async Operations** 106 + - [ ] Eio-based file reading 107 + - [ ] Async chunked transmission 108 + - [ ] Streaming large images 109 + - [ ] Non-blocking operations 110 + 111 + **Buf_read/Buf_write Integration** 112 + - [ ] Use Eio.Buf_read for parsing responses 113 + - [ ] Use Eio.Buf_write for efficient serialization 114 + - [ ] Zero-copy optimizations 115 + 116 + ### Phase 5: Quality & Documentation 117 + 118 + **Testing** 119 + - [ ] Unit tests for escape sequence generation 120 + - [ ] Unit tests for chunking logic 121 + - [ ] Unit tests for base64 encoding 122 + - [ ] Integration tests with mock terminal 123 + - [ ] Animation frame sequencing tests 124 + - [ ] Example binaries for manual testing 125 + 126 + **Documentation** 127 + - [ ] Complete API documentation 128 + - [ ] Usage examples 129 + - [ ] Animation tutorial 130 + - [ ] Performance guidelines 131 + - [ ] Terminal compatibility matrix 132 + 133 + **Examples** 134 + - [ ] Simple image display 135 + - [ ] Animated GIF-style display 136 + - [ ] Image gallery 137 + - [ ] Terminal image viewer 138 + - [ ] Progress indicator with graphics 139 + 140 + ## Design Decisions 141 + 142 + ### Transmission Format 143 + 144 + The Kitty graphics protocol uses APC escape sequences: 145 + ``` 146 + ESC _G<control>;<payload>ESC \ 147 + ``` 148 + 149 + - **Control data**: Comma-separated key=value pairs 150 + - **Payload**: Base64-encoded image data (for direct transmission) 151 + 152 + ### Key Control Parameters 153 + 154 + | Key | Description | Values | 155 + |-----|-------------|--------| 156 + | `a` | Action | `t`=transmit+display, `T`=transmit only, `p`=display, `d`=delete, `f`=frame, `a`=animate | 157 + | `f` | Format | `24`=RGB, `32`=RGBA, `100`=PNG | 158 + | `t` | Transmission | `d`=direct, `f`=file, `t`=temp, `s`=shared | 159 + | `i` | Image ID | Number or string (max 24 chars) | 160 + | `p` | Placement ID | Number | 161 + | `s` | Width | Pixels | 162 + | `v` | Height | Pixels | 163 + | `c` | Columns | Terminal cells | 164 + | `r` | Rows | Terminal cells | 165 + | `x` | X offset | Pixels | 166 + | `y` | Y offset | Pixels | 167 + | `z` | Z-index | Integer | 168 + | `C` | Cursor | `0`=don't move, `1`=move | 169 + | `o` | Compression | `z`=zlib | 170 + | `m` | More chunks | `1`=more coming, `0`=last | 171 + 172 + ### Animation-Specific Parameters 173 + 174 + | Key | Description | Values | 175 + |-----|-------------|--------| 176 + | `z` | Frame number | Integer (1-based) | 177 + | `g` | Gap | Milliseconds (0-65535) | 178 + | `c` | Composition | `0`=blend, `1`=overwrite | 179 + | `v` | Loop | Number (0=infinite) | 180 + | `s` | Animation state | `1`=stop, `2`=run | 181 + 182 + ### Chunking Strategy 183 + 184 + - Maximum chunk size: 4096 bytes (protocol limit) 185 + - For images larger than 4096 bytes when base64-encoded: 186 + 1. First chunk: Include full control data, set `m=1` 187 + 2. Middle chunks: Only payload data, set `m=1` 188 + 3. Last chunk: Only payload data, set `m=0` 189 + 190 + ### Error Handling 191 + 192 + - Validate all parameters before encoding 193 + - Check file existence for file-based transmission 194 + - Ensure image data dimensions match declared width/height 195 + - Validate ID formats (image_id max 24 chars) 196 + - Raise `Invalid_argument` for out-of-range values 197 + 198 + ### Memory Efficiency 199 + 200 + - Use `bytes` for image data (mutable, efficient) 201 + - Stream large files rather than loading into memory 202 + - Base64 encode in chunks to limit memory usage 203 + - Consider compression for large images 204 + 205 + ## Future Enhancements 206 + 207 + ### Additional Protocols 208 + - [ ] Kitty keyboard protocol 209 + - [ ] Kitty clipboard protocol 210 + - [ ] Synchronized output protocol 211 + 212 + ### Utilities 213 + - [ ] Image format detection 214 + - [ ] Automatic format conversion 215 + - [ ] Image resizing/scaling helpers 216 + - [ ] Color quantization 217 + 218 + ### Integration 219 + - [ ] Notty integration 220 + - [ ] Direct terminal detection 221 + - [ ] Fallback for non-Kitty terminals 222 + 223 + ## Notes 224 + 225 + ### Dependencies Rationale 226 + 227 + - **fmt**: Already used in Textsize, provides consistent formatting API 228 + - **base64**: Standard library for base64 encoding/decoding, well-tested 229 + - **camlzip**: ZLIB compression support, mature library 230 + 231 + ### No Image Library Dependencies 232 + 233 + Deliberately not depending on image libraries (camlimages, imagelib) because: 234 + 1. Keeps library lightweight 235 + 2. Users may already have image data in memory 236 + 3. PNG format can be passed through without decoding 237 + 4. Allows flexibility in image source (file, network, generated, etc.) 238 + 5. User can choose their preferred image library 239 + 240 + ### Compatibility 241 + 242 + **Terminals with full support:** 243 + - Kitty (v0.40.0+ for text sizing, earlier for graphics) 244 + - Ghostty (with graphics protocol support) 245 + 246 + **Terminals with partial support:** 247 + - WezTerm (graphics protocol) 248 + - Konsole (partial graphics support) 249 + 250 + **Graceful degradation:** 251 + - Other terminals typically ignore unknown escape sequences 252 + - No visible output, but no crashes or errors
+8 -5
tgp/dune-project
··· 1 1 (lang dune 3.0) 2 - (name textsize) 2 + (name termext) 3 3 (version 0.1.0) 4 4 5 5 (generate_opam_files true) 6 6 7 - (source (github username/textsize)) 7 + (source (github username/termext)) 8 8 (license MIT) 9 9 (authors "Anonymous") 10 10 (maintainers "Anonymous") 11 11 12 12 (package 13 - (name textsize) 14 - (synopsis "OCaml implementation of the Kitty text sizing protocol") 15 - (description "A clean, standalone library for generating escape sequences to render text in different sizes using the Kitty terminal's text sizing protocol.") 13 + (name termext) 14 + (synopsis "OCaml implementation of Kitty terminal protocols") 15 + (description "A library for generating escape sequences for Kitty terminal protocols including text sizing and graphics display.") 16 16 (depends 17 17 (ocaml (>= 4.14)) 18 + (fmt (>= 0.9.0)) 19 + (base64 (>= 3.5.0)) 20 + (camlzip (>= 1.11)) 18 21 dune))
+22 -22
tgp/example/demo.ml
··· 4 4 print_endline "=== Kitty Text Sizing Protocol Demo ===\n"; 5 5 6 6 (* Simple convenience functions *) 7 - print_string (Textsize.double "Double sized text"); 7 + print_string (Termext.Textsize.double "Double sized text"); 8 8 print_endline "\n"; 9 9 10 - print_string (Textsize.triple "Triple sized text"); 10 + print_string (Termext.Textsize.triple "Triple sized text"); 11 11 print_endline "\n\n"; 12 12 13 - print_string (Textsize.quadruple "Quadruple sized text"); 13 + print_string (Termext.Textsize.quadruple "Quadruple sized text"); 14 14 print_endline "\n\n\n"; 15 15 16 16 (* Scaled text *) 17 - print_string (Textsize.scaled 5 "Scale 5 text"); 17 + print_string (Termext.Textsize.scaled 5 "Scale 5 text"); 18 18 print_endline "\n\n\n\n"; 19 19 20 20 (* Fractional scaling *) 21 - print_string (Textsize.half "Half sized text"); 21 + print_string (Termext.Textsize.half "Half sized text"); 22 22 print_newline (); 23 23 24 24 (* Superscripts and subscripts *) 25 25 print_string "E=mc"; 26 - print_string (Textsize.superscript "2"); 26 + print_string (Termext.Textsize.superscript "2"); 27 27 print_newline (); 28 28 29 29 print_string "H"; 30 - print_string (Textsize.subscript "2"); 30 + print_string (Termext.Textsize.subscript "2"); 31 31 print_string "O"; 32 32 print_newline (); 33 33 34 34 (* Custom metadata *) 35 35 print_newline (); 36 - let custom = Textsize.v ~scale:3 ~width:5 () in 37 - print_string (Textsize.render custom "Custom sized text"); 36 + let custom = Termext.Textsize.v ~scale:3 ~width:5 () in 37 + print_string (Termext.Textsize.render custom "Custom sized text"); 38 38 print_endline "\n\n"; 39 39 40 40 (* Fractional with alignment *) 41 - let aligned = Textsize.v ~fraction:(1, 3) ~vertical:`Center ~horizontal:`Center () in 42 - print_string (Textsize.render aligned "Centered 1/3 size text"); 41 + let aligned = Termext.Textsize.v ~fraction:(1, 3) ~vertical:`Center ~horizontal:`Center () in 42 + print_string (Termext.Textsize.render aligned "Centered 1/3 size text"); 43 43 print_newline (); 44 44 45 45 (* Fmt-style combinators *) 46 46 print_endline "\n=== Fmt-style Examples ===\n"; 47 47 48 48 (* Basic pp formatter *) 49 - Fmt.pr "This is %a text\n" Textsize.pp_double "double-sized"; 50 - Fmt.pr "This is %a text\n" Textsize.pp_triple "triple-sized"; 49 + Fmt.pr "This is %a text\n" Termext.Textsize.pp_double "double-sized"; 50 + Fmt.pr "This is %a text\n" Termext.Textsize.pp_triple "triple-sized"; 51 51 52 52 (* Superscripts and subscripts with Fmt *) 53 - Fmt.pr "E=mc%a\n" Textsize.pp_superscript "2"; 54 - Fmt.pr "H%aO\n" Textsize.pp_subscript "2"; 53 + Fmt.pr "E=mc%a\n" Termext.Textsize.pp_superscript "2"; 54 + Fmt.pr "H%aO\n" Termext.Textsize.pp_subscript "2"; 55 55 56 56 (* Styled combinators wrapping other formatters *) 57 - Fmt.pr "The answer is %a\n" (Textsize.styled_double Fmt.int) 42; 58 - Fmt.pr "Pi is approximately %a\n" (Textsize.styled_triple Fmt.float) 3.14159; 57 + Fmt.pr "The answer is %a\n" (Termext.Textsize.styled_double Fmt.int) 42; 58 + Fmt.pr "Pi is approximately %a\n" (Termext.Textsize.styled_triple Fmt.float) 3.14159; 59 59 60 60 (* Complex formatting with Fmt *) 61 61 Fmt.pr "Temperature: %a°C (that's %a°F)\n" 62 - (Textsize.styled_superscript Fmt.int) 25 63 - (Textsize.styled_subscript Fmt.int) 77; 62 + (Termext.Textsize.styled_superscript Fmt.int) 25 63 + (Termext.Textsize.styled_subscript Fmt.int) 77; 64 64 65 65 (* Custom metadata with Fmt *) 66 - let custom_meta = Textsize.v ~scale:4 () in 67 - Fmt.pr "\n%a\n" (Textsize.pp custom_meta) "Custom Fmt-styled text"; 66 + let custom_meta = Termext.Textsize.v ~scale:4 () in 67 + Fmt.pr "\n%a\n" (Termext.Textsize.pp custom_meta) "Custom Fmt-styled text"; 68 68 69 69 (* Composing with other Fmt combinators *) 70 70 Fmt.pr "\nList items: %a\n" 71 - (Fmt.list ~sep:(Fmt.any ", ") (Textsize.styled_double Fmt.string)) 71 + (Fmt.list ~sep:(Fmt.any ", ") (Termext.Textsize.styled_double Fmt.string)) 72 72 ["apple"; "banana"; "cherry"]; 73 73 74 74 print_endline "\n=== Demo Complete ===";
+7 -1
tgp/example/dune
··· 1 1 (executable 2 2 (name demo) 3 - (libraries textsize fmt)) 3 + (modules demo) 4 + (libraries termext fmt)) 5 + 6 + (executable 7 + (name graphics_demo) 8 + (modules graphics_demo) 9 + (libraries termext fmt unix))
+156
tgp/example/graphics_demo.ml
··· 1 + (* Example demonstrating the Termgraph module of termext library *) 2 + 3 + let () = 4 + print_endline "=== Kitty Graphics Protocol Demo ===\n"; 5 + 6 + (* Example 1: Display a PNG file *) 7 + print_endline "--- Example 1: Display PNG file ---"; 8 + let png_path = "example.png" in 9 + (* Simple convenience function *) 10 + print_string (Termext.Termgraph.display_png_file png_path); 11 + print_endline "\n"; 12 + 13 + (* Example 2: Display PNG file with size control *) 14 + print_endline "--- Example 2: PNG file with sizing ---"; 15 + print_string (Termext.Termgraph.display_png_file 16 + ~rows:10 17 + ~columns:20 18 + png_path); 19 + print_endline "\n"; 20 + 21 + (* Example 3: Using the full placement API *) 22 + print_endline "--- Example 3: Full placement control ---"; 23 + let img_id = Termext.Termgraph.image_id_of_string "demo-image" in 24 + let placement = Termext.Termgraph.v 25 + ~image_id:img_id 26 + ~x:10 27 + ~y:5 28 + ~rows:15 29 + ~columns:30 30 + ~z_index:10 31 + (Termext.Termgraph.File { path = png_path }) 32 + () 33 + in 34 + print_string (Termext.Termgraph.render placement); 35 + print_endline "\n"; 36 + 37 + (* Example 4: Direct transmission with raw PNG data *) 38 + print_endline "--- Example 4: Direct transmission ---"; 39 + (* In a real use case, you would load PNG data from somewhere *) 40 + let png_data = Bytes.of_string "\x89PNG\r\n\x1a\n..." in 41 + print_string (Termext.Termgraph.display_png_bytes 42 + ~w:100 43 + ~h:100 44 + ~rows:10 45 + ~columns:10 46 + png_data); 47 + print_endline "\n"; 48 + 49 + (* Example 5: Fmt-style formatters *) 50 + print_endline "--- Example 5: Fmt formatters ---"; 51 + let img = Termext.Termgraph.v 52 + (Termext.Termgraph.File { path = "logo.png" }) 53 + () 54 + in 55 + Fmt.pr "Here's our logo: %a\n" (Termext.Termgraph.pp img) (); 56 + print_endline ""; 57 + 58 + (* Example 6: Animation *) 59 + print_endline "--- Example 6: Animation frames ---"; 60 + let anim_id = Termext.Termgraph.image_id_of_string "animation" in 61 + 62 + (* Upload frame 1 *) 63 + let frame1 = Termext.Termgraph.Animation.frame 64 + ~gap:100 (* 100ms gap *) 65 + (Termext.Termgraph.File { path = "frame1.png" }) 66 + 1 (* Frame number *) 67 + in 68 + print_string (Termext.Termgraph.Animation.render_frame anim_id frame1); 69 + 70 + (* Upload frame 2 *) 71 + let frame2 = Termext.Termgraph.Animation.frame 72 + ~gap:100 73 + ~composition:Termext.Termgraph.Animation.Blend 74 + (Termext.Termgraph.File { path = "frame2.png" }) 75 + 2 76 + in 77 + print_string (Termext.Termgraph.Animation.render_frame anim_id frame2); 78 + 79 + (* Upload frame 3 *) 80 + let frame3 = Termext.Termgraph.Animation.frame 81 + ~gap:100 82 + (Termext.Termgraph.File { path = "frame3.png" }) 83 + 3 84 + in 85 + print_string (Termext.Termgraph.Animation.render_frame anim_id frame3); 86 + 87 + (* Set animation to loop infinitely *) 88 + let loop_control = Termext.Termgraph.Animation.Set_loop 0 in 89 + print_string (Termext.Termgraph.Animation.render_control anim_id loop_control); 90 + print_endline "\n"; 91 + 92 + (* Example 7: Using Fmt for animation *) 93 + print_endline "--- Example 7: Animation with Fmt ---"; 94 + Fmt.pr "Frame 1: %a\n" 95 + (Termext.Termgraph.pp_animation_frame anim_id frame1) (); 96 + Fmt.pr "Setting loop: %a\n" 97 + (Termext.Termgraph.pp_animation_control anim_id (Termext.Termgraph.Animation.Set_loop 3)) (); 98 + print_endline ""; 99 + 100 + (* Example 8: Deletion *) 101 + print_endline "--- Example 8: Deleting images ---"; 102 + Unix.sleep 2; (* Show images for 2 seconds *) 103 + 104 + (* Delete specific image *) 105 + print_string (Termext.Termgraph.delete_by_id img_id); 106 + 107 + (* Or delete all images *) 108 + print_string (Termext.Termgraph.delete_all ()); 109 + print_endline ""; 110 + 111 + (* Example 9: Complex scene with z-index *) 112 + print_endline "--- Example 9: Layered images ---"; 113 + (* Background layer *) 114 + let bg = Termext.Termgraph.v 115 + ~image_id:(Termext.Termgraph.image_id_of_string "background") 116 + ~z_index:(-10) 117 + ~rows:20 118 + ~columns:40 119 + (Termext.Termgraph.File { path = "background.png" }) 120 + () 121 + in 122 + print_string (Termext.Termgraph.render bg); 123 + 124 + (* Foreground layer *) 125 + let fg = Termext.Termgraph.v 126 + ~image_id:(Termext.Termgraph.image_id_of_string "foreground") 127 + ~z_index:10 128 + ~x:50 129 + ~y:50 130 + ~rows:10 131 + ~columns:20 132 + (Termext.Termgraph.File { path = "sprite.png" }) 133 + () 134 + in 135 + print_string (Termext.Termgraph.render fg); 136 + print_endline "\n"; 137 + 138 + (* Example 10: Combining text sizing and graphics *) 139 + print_endline "--- Example 10: Mixed text and graphics ---"; 140 + print_string (Termext.Textsize.double "Large Title"); 141 + print_endline "\n"; 142 + 143 + let small_img = Termext.Termgraph.v 144 + ~rows:5 145 + ~columns:10 146 + (Termext.Termgraph.File { path = "icon.png" }) 147 + () 148 + in 149 + Fmt.pr "Icon: %a Description text here\n" 150 + (Termext.Termgraph.pp small_img) (); 151 + 152 + print_string (Termext.Textsize.subscript "footnote"); 153 + print_endline "\n"; 154 + 155 + print_endline "=== Demo Complete ==="; 156 + print_endline "Note: This demo requires PNG files and a Kitty-compatible terminal.";
+4 -3
tgp/src/dune
··· 1 1 (library 2 - (name textsize) 3 - (public_name textsize) 4 - (libraries fmt)) 2 + (name termext) 3 + (public_name termext) 4 + (libraries fmt base64 zip) 5 + (modules_without_implementation termgraph))
+229
tgp/src/termgraph.mli
··· 1 + (** OCaml implementation of the Kitty graphics protocol. 2 + 3 + This library provides a clean API for generating escape sequences to display 4 + images and animations in terminals that support the Kitty graphics protocol. 5 + 6 + The protocol uses APC (Application Program Command) escape sequences to transmit 7 + and display images with extensive control over placement, sizing, and composition. 8 + 9 + See {{: https://sw.kovidgoyal.net/kitty/graphics-protocol/} Kitty Graphics Protocol} 10 + for the complete specification. 11 + *) 12 + 13 + (** {1 Core Types} *) 14 + 15 + (** Image format for raw pixel data. *) 16 + type format = 17 + | RGB (** 24-bit RGB (3 bytes per pixel) *) 18 + | RGBA (** 32-bit RGBA (4 bytes per pixel) *) 19 + | PNG (** PNG format *) 20 + 21 + (** Compression method for image data. *) 22 + type compression = 23 + | No_compression 24 + | Zlib 25 + 26 + (** Transmission medium. *) 27 + type transmission = 28 + | Direct of { 29 + format : format; 30 + data : bytes; 31 + width : int; (** Width in pixels *) 32 + height : int; (** Height in pixels *) 33 + compression : compression; 34 + } 35 + | File of { 36 + path : string; 37 + } 38 + 39 + (** Image identifier (number or string up to 24 chars). *) 40 + type image_id = private string 41 + 42 + (** Placement identifier. *) 43 + type placement_id = private int 44 + 45 + (** {1 Image Identifiers} *) 46 + 47 + (** [image_id_of_int n] creates an image ID from an integer. 48 + @raise Invalid_argument if [n] is negative *) 49 + val image_id_of_int : int -> image_id 50 + 51 + (** [image_id_of_string s] creates an image ID from a string. 52 + @raise Invalid_argument if [s] is empty or longer than 24 characters *) 53 + val image_id_of_string : string -> image_id 54 + 55 + (** [placement_id_of_int n] creates a placement ID from an integer. 56 + @raise Invalid_argument if [n] is negative *) 57 + val placement_id_of_int : int -> placement_id 58 + 59 + (** {1 Placement Configuration} *) 60 + 61 + (** Placement configuration for displaying images. *) 62 + type placement 63 + 64 + (** [v ?image_id ?placement_id ?x ?y ?width ?height ?rows ?columns ?z_index 65 + ?cursor_movement transmission ()] creates a placement configuration. 66 + 67 + @param image_id Image identifier for reuse/deletion 68 + @param placement_id Placement identifier for this specific display 69 + @param x Left edge offset in pixels 70 + @param y Top edge offset in pixels 71 + @param width Width in pixels (scales image if different from source) 72 + @param height Height in pixels (scales image if different from source) 73 + @param rows Height in terminal cells 74 + @param columns Width in terminal cells 75 + @param z_index Z-order for layering (-2^31 to 2^31-1, default 0) 76 + @param cursor_movement If false, cursor doesn't move after display (default true) 77 + @param transmission The image data transmission method *) 78 + val v : 79 + ?image_id:image_id -> 80 + ?placement_id:placement_id -> 81 + ?x:int -> 82 + ?y:int -> 83 + ?width:int -> 84 + ?height:int -> 85 + ?rows:int -> 86 + ?columns:int -> 87 + ?z_index:int -> 88 + ?cursor_movement:bool -> 89 + transmission -> 90 + unit -> 91 + placement 92 + 93 + (** {1 Rendering} *) 94 + 95 + (** [render placement] generates the complete escape sequence(s) for the placement. 96 + For large images that require chunking, this returns a single string with all chunks. *) 97 + val render : placement -> string 98 + 99 + (** [render_chunked placement] generates escape sequences as a list of chunks. 100 + Useful for streaming large images or interleaving with other output. *) 101 + val render_chunked : placement -> string list 102 + 103 + (** {1 Deletion} *) 104 + 105 + (** Delete images or placements. *) 106 + module Delete : sig 107 + (** Deletion target. *) 108 + type target = 109 + | By_id of image_id 110 + | By_image_id of image_id 111 + | By_placement_id of placement_id 112 + | At_cursor 113 + | All 114 + 115 + (** [render target] generates the escape sequence to delete images. *) 116 + val render : target -> string 117 + end 118 + 119 + (** {1 Animation} *) 120 + 121 + (** Animation support for multi-frame images. *) 122 + module Animation : sig 123 + (** Frame composition mode. *) 124 + type composition = 125 + | Blend (** Alpha blend with previous frame *) 126 + | Overwrite (** Replace previous frame *) 127 + 128 + (** Animation frame configuration. *) 129 + type frame 130 + 131 + (** [frame ?composition ?gap transmission frame_number] creates a frame configuration. 132 + 133 + @param composition How to compose this frame with previous frames 134 + @param gap Gap before next frame in milliseconds (0-65535) 135 + @param transmission The frame image data 136 + @param frame_number Frame index (1-based) *) 137 + val frame : 138 + ?composition:composition -> 139 + ?gap:int -> 140 + transmission -> 141 + int -> 142 + frame 143 + 144 + (** Animation control. *) 145 + type control = 146 + | Set_gap of { 147 + frame_number : int; (** Which frame to modify *) 148 + gap : int; (** New gap in milliseconds *) 149 + } 150 + | Set_loop of int (** Number of loops (0 = infinite) *) 151 + | Stop (** Stop animation *) 152 + | Run (** Resume animation *) 153 + 154 + (** [render_frame image_id frame] generates escape sequence for transmitting an animation frame. 155 + 156 + @param image_id The animation's image identifier *) 157 + val render_frame : image_id -> frame -> string 158 + 159 + (** [render_control image_id control] generates escape sequence for animation control. *) 160 + val render_control : image_id -> control -> string 161 + end 162 + 163 + (** {1 Fmt-style Formatters} *) 164 + 165 + (** [pp placement] creates a Fmt formatter that displays an image. 166 + 167 + Example: 168 + {[ 169 + let img = Graphics.v (Graphics.File { path = "image.png" }) () in 170 + Fmt.pr "Here's an image: %a" Graphics.pp img 171 + ]} 172 + *) 173 + val pp : placement -> unit Fmt.t 174 + 175 + (** [pp_delete target] creates a Fmt formatter that deletes images. 176 + 177 + Example: 178 + {[ 179 + Fmt.pr "Clearing screen: %a" Graphics.pp_delete Graphics.Delete.All 180 + ]} 181 + *) 182 + val pp_delete : Delete.target -> unit Fmt.t 183 + 184 + (** [pp_animation_frame image_id frame] creates a Fmt formatter for animation frames. *) 185 + val pp_animation_frame : image_id -> Animation.frame -> unit Fmt.t 186 + 187 + (** [pp_animation_control image_id control] creates a Fmt formatter for animation control. *) 188 + val pp_animation_control : image_id -> Animation.control -> unit Fmt.t 189 + 190 + (** {1 Convenience Functions} *) 191 + 192 + (** [display_png_file ?x ?y ?width ?height ?rows ?columns path] displays a PNG file. 193 + 194 + @param path Path to PNG file 195 + @return Escape sequence string *) 196 + val display_png_file : 197 + ?x:int -> 198 + ?y:int -> 199 + ?width:int -> 200 + ?height:int -> 201 + ?rows:int -> 202 + ?columns:int -> 203 + string -> 204 + string 205 + 206 + (** [display_png_bytes ?x ?y ?width ?height ?rows ?columns ~width:w ~height:h data] 207 + displays PNG data from bytes. 208 + 209 + @param width Image width in pixels 210 + @param height Image height in pixels 211 + @param data PNG-encoded image data 212 + @return Escape sequence string *) 213 + val display_png_bytes : 214 + ?x:int -> 215 + ?y:int -> 216 + ?width:int -> 217 + ?height:int -> 218 + ?rows:int -> 219 + ?columns:int -> 220 + w:int -> 221 + h:int -> 222 + bytes -> 223 + string 224 + 225 + (** [delete_all ()] generates escape sequence to delete all images. *) 226 + val delete_all : unit -> string 227 + 228 + (** [delete_by_id id] generates escape sequence to delete image by ID. *) 229 + val delete_by_id : image_id -> string
+34
tgp/termext.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + version: "0.1.0" 4 + synopsis: "OCaml implementation of Kitty terminal protocols" 5 + description: 6 + "A library for generating escape sequences for Kitty terminal protocols including text sizing and graphics display." 7 + maintainer: ["Anonymous"] 8 + authors: ["Anonymous"] 9 + license: "MIT" 10 + homepage: "https://github.com/username/termext" 11 + bug-reports: "https://github.com/username/termext/issues" 12 + depends: [ 13 + "ocaml" {>= "4.14"} 14 + "fmt" {>= "0.9.0"} 15 + "base64" {>= "3.5.0"} 16 + "camlzip" {>= "1.11"} 17 + "dune" {>= "3.0"} 18 + "odoc" {with-doc} 19 + ] 20 + build: [ 21 + ["dune" "subst"] {dev} 22 + [ 23 + "dune" 24 + "build" 25 + "-p" 26 + name 27 + "-j" 28 + jobs 29 + "@install" 30 + "@runtest" {with-test} 31 + "@doc" {with-doc} 32 + ] 33 + ] 34 + dev-repo: "git+https://github.com/username/termext.git"
+1 -1
tgp/test/dune
··· 1 1 (test 2 2 (name test_textsize) 3 - (libraries textsize)) 3 + (libraries termext))
+17 -17
tgp/test/test_textsize.ml
··· 13 13 14 14 (* Test scale validation *) 15 15 test "scale validation - valid range" (fun () -> 16 - let _ = Textsize.v ~scale:1 () in 17 - let _ = Textsize.v ~scale:7 () in 16 + let _ = Termext.Textsize.v ~scale:1 () in 17 + let _ = Termext.Textsize.v ~scale:7 () in 18 18 () 19 19 ); 20 20 21 21 test "scale validation - rejects invalid" (fun () -> 22 22 try 23 - let _ = Textsize.v ~scale:0 () in 23 + let _ = Termext.Textsize.v ~scale:0 () in 24 24 failwith "Should have raised Invalid_argument" 25 25 with Invalid_argument _ -> () 26 26 ); 27 27 28 28 (* Test width validation *) 29 29 test "width validation - valid range" (fun () -> 30 - let _ = Textsize.v ~width:0 () in 31 - let _ = Textsize.v ~width:7 () in 30 + let _ = Termext.Textsize.v ~width:0 () in 31 + let _ = Termext.Textsize.v ~width:7 () in 32 32 () 33 33 ); 34 34 35 35 (* Test escape sequence generation *) 36 36 test "double escape sequence" (fun () -> 37 - let result = Textsize.double "test" in 37 + let result = Termext.Textsize.double "test" in 38 38 assert (String.length result > 0); 39 39 assert (result = "\x1b]66;s=2;test\x07") 40 40 ); 41 41 42 42 test "triple escape sequence" (fun () -> 43 - let result = Textsize.triple "hello" in 43 + let result = Termext.Textsize.triple "hello" in 44 44 assert (result = "\x1b]66;s=3;hello\x07") 45 45 ); 46 46 47 47 test "half escape sequence" (fun () -> 48 - let result = Textsize.half "tiny" in 48 + let result = Termext.Textsize.half "tiny" in 49 49 assert (result = "\x1b]66;n=1:d=2;tiny\x07") 50 50 ); 51 51 52 52 test "superscript escape sequence" (fun () -> 53 - let result = Textsize.superscript "2" in 53 + let result = Termext.Textsize.superscript "2" in 54 54 assert (result = "\x1b]66;n=1:d=2:v=2;2\x07") 55 55 ); 56 56 57 57 test "subscript escape sequence" (fun () -> 58 - let result = Textsize.subscript "2" in 58 + let result = Termext.Textsize.subscript "2" in 59 59 assert (result = "\x1b]66;n=1:d=2:v=0;2\x07") 60 60 ); 61 61 62 62 (* Test custom metadata *) 63 63 test "custom metadata - scale and width" (fun () -> 64 - let metadata = Textsize.v ~scale:3 ~width:5 () in 65 - let result = Textsize.render metadata "custom" in 64 + let metadata = Termext.Textsize.v ~scale:3 ~width:5 () in 65 + let result = Termext.Textsize.render metadata "custom" in 66 66 assert (result = "\x1b]66;s=3:w=5;custom\x07") 67 67 ); 68 68 69 69 test "custom metadata - fractional" (fun () -> 70 - let metadata = Textsize.v ~fraction:(2, 3) () in 71 - let result = Textsize.render metadata "frac" in 70 + let metadata = Termext.Textsize.v ~fraction:(2, 3) () in 71 + let result = Termext.Textsize.render metadata "frac" in 72 72 assert (result = "\x1b]66;n=2:d=3;frac\x07") 73 73 ); 74 74 75 75 test "empty metadata" (fun () -> 76 - let result = Textsize.render (Textsize.v ()) "plain" in 76 + let result = Termext.Textsize.render (Termext.Textsize.v ()) "plain" in 77 77 assert (result = "\x1b]66;;plain\x07") 78 78 ); 79 79 80 80 (* Test text length validation *) 81 81 test "text length validation - accepts valid" (fun () -> 82 82 let text = String.make 4096 'a' in 83 - let _ = Textsize.double text in 83 + let _ = Termext.Textsize.double text in 84 84 () 85 85 ); 86 86 87 87 test "text length validation - rejects oversized" (fun () -> 88 88 let text = String.make 4097 'a' in 89 89 try 90 - let _ = Textsize.double text in 90 + let _ = Termext.Textsize.double text in 91 91 failwith "Should have raised Invalid_argument" 92 92 with Invalid_argument _ -> () 93 93 );
-31
tgp/textsize.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - version: "0.1.0" 4 - synopsis: "OCaml implementation of the Kitty text sizing protocol" 5 - description: 6 - "A clean, standalone library for generating escape sequences to render text in different sizes using the Kitty terminal's text sizing protocol." 7 - maintainer: ["Anonymous"] 8 - authors: ["Anonymous"] 9 - license: "MIT" 10 - homepage: "https://github.com/username/textsize" 11 - bug-reports: "https://github.com/username/textsize/issues" 12 - depends: [ 13 - "ocaml" {>= "4.14"} 14 - "dune" {>= "3.0"} 15 - "odoc" {with-doc} 16 - ] 17 - build: [ 18 - ["dune" "subst"] {dev} 19 - [ 20 - "dune" 21 - "build" 22 - "-p" 23 - name 24 - "-j" 25 - jobs 26 - "@install" 27 - "@runtest" {with-test} 28 - "@doc" {with-doc} 29 - ] 30 - ] 31 - dev-repo: "git+https://github.com/username/textsize.git"