···11+(** OCaml implementation of the Kitty text sizing protocol *)
22+33+(* Types *)
44+55+type scale = int
66+type width = int
77+type numerator = int
88+type denominator = int
99+1010+type vertical_align =
1111+ | Bottom
1212+ | Center
1313+ | Top
1414+1515+type horizontal_align =
1616+ | Left
1717+ | Center
1818+ | Right
1919+2020+type metadata = {
2121+ scale : scale option;
2222+ width : width option;
2323+ numerator : numerator option;
2424+ denominator : denominator option;
2525+ vertical : vertical_align option;
2626+ horizontal : horizontal_align option;
2727+}
2828+2929+(* Constructors with validation *)
3030+3131+let make_scale n =
3232+ if n < 1 || n > 7 then
3333+ invalid_arg (Printf.sprintf "scale must be in range 1-7, got %d" n)
3434+ else
3535+ n
3636+3737+let make_width n =
3838+ if n < 0 || n > 7 then
3939+ invalid_arg (Printf.sprintf "width must be in range 0-7, got %d" n)
4040+ else
4141+ n
4242+4343+let make_numerator n =
4444+ if n < 0 || n > 15 then
4545+ invalid_arg (Printf.sprintf "numerator must be in range 0-15, got %d" n)
4646+ else
4747+ n
4848+4949+let make_denominator n =
5050+ if n < 0 || n > 15 then
5151+ invalid_arg (Printf.sprintf "denominator must be in range 0-15, got %d" n)
5252+ else
5353+ n
5454+5555+(* Metadata creation *)
5656+5757+let empty = {
5858+ scale = None;
5959+ width = None;
6060+ numerator = None;
6161+ denominator = None;
6262+ vertical = None;
6363+ horizontal = None;
6464+}
6565+6666+let with_scale s metadata = { metadata with scale = Some s }
6767+let with_width w metadata = { metadata with width = Some w }
6868+6969+let with_fraction num den metadata =
7070+ { metadata with numerator = Some num; denominator = Some den }
7171+7272+let with_vertical_align v metadata = { metadata with vertical = Some v }
7373+let with_horizontal_align h metadata = { metadata with horizontal = Some h }
7474+7575+(* Conversion helpers *)
7676+7777+let vertical_align_to_int = function
7878+ | Bottom -> 0
7979+ | Center -> 1
8080+ | Top -> 2
8181+8282+let horizontal_align_to_int = function
8383+ | Left -> 0
8484+ | Center -> 1
8585+ | Right -> 2
8686+8787+(* Escape sequence generation *)
8888+8989+let metadata_to_string metadata =
9090+ let parts = [] in
9191+ let parts = match metadata.scale with
9292+ | Some s -> (Printf.sprintf "s=%d" s) :: parts
9393+ | None -> parts
9494+ in
9595+ let parts = match metadata.width with
9696+ | Some w -> (Printf.sprintf "w=%d" w) :: parts
9797+ | None -> parts
9898+ in
9999+ let parts = match metadata.numerator with
100100+ | Some n -> (Printf.sprintf "n=%d" n) :: parts
101101+ | None -> parts
102102+ in
103103+ let parts = match metadata.denominator with
104104+ | Some d -> (Printf.sprintf "d=%d" d) :: parts
105105+ | None -> parts
106106+ in
107107+ let parts = match metadata.vertical with
108108+ | Some v -> (Printf.sprintf "v=%d" (vertical_align_to_int v)) :: parts
109109+ | None -> parts
110110+ in
111111+ let parts = match metadata.horizontal with
112112+ | Some h -> (Printf.sprintf "h=%d" (horizontal_align_to_int h)) :: parts
113113+ | None -> parts
114114+ in
115115+ String.concat ":" (List.rev parts)
116116+117117+let render metadata text =
118118+ (* Validate text length (max 4096 bytes of UTF-8) *)
119119+ let text_len = String.length text in
120120+ if text_len > 4096 then
121121+ invalid_arg (Printf.sprintf "text exceeds 4096 bytes (got %d)" text_len);
122122+123123+ let metadata_str = metadata_to_string metadata in
124124+125125+ (* OSC 66 ; metadata ; text BEL *)
126126+ (* Using \x1b for ESC and \x07 for BEL *)
127127+ if metadata_str = "" then
128128+ Printf.sprintf "\x1b]66;;%s\x07" text
129129+ else
130130+ Printf.sprintf "\x1b]66;%s;%s\x07" metadata_str text
131131+132132+let render_to_channel oc metadata text =
133133+ output_string oc (render metadata text);
134134+ flush oc
135135+136136+(* Convenience functions *)
137137+138138+let double text =
139139+ render (with_scale (make_scale 2) empty) text
140140+141141+let triple text =
142142+ render (with_scale (make_scale 3) empty) text
143143+144144+let quadruple text =
145145+ render (with_scale (make_scale 4) empty) text
146146+147147+let half text =
148148+ render
149149+ (with_fraction (make_numerator 1) (make_denominator 2) empty)
150150+ text
151151+152152+let superscript text =
153153+ render
154154+ (empty
155155+ |> with_fraction (make_numerator 1) (make_denominator 2)
156156+ |> with_vertical_align Top)
157157+ text
158158+159159+let subscript text =
160160+ render
161161+ (empty
162162+ |> with_fraction (make_numerator 1) (make_denominator 2)
163163+ |> with_vertical_align Bottom)
164164+ text
165165+166166+let scaled n text =
167167+ render (with_scale (make_scale n) empty) text
+141
tgp/src/textsize.mli
···11+(** OCaml implementation of the Kitty text sizing protocol.
22+33+ This library provides a clean API for generating escape sequences to render
44+ text in different sizes within terminals that support the Kitty text sizing
55+ protocol (introduced in kitty v0.40.0).
66+77+ The protocol allows rendering text at multiple sizes both larger and smaller
88+ than the base text, enabling typographic features like headlines, superscripts,
99+ and subscripts.
1010+*)
1111+1212+(** {1 Types} *)
1313+1414+(** Scale factor for text rendering (1-7).
1515+1616+ Controls the overall size of the text block. A scale of [s] will render text
1717+ in a block of [s * w] by [s] cells (where [w] is the width parameter). *)
1818+type scale = private int
1919+2020+(** Width in cells for text rendering (0-7).
2121+2222+ When set to 0, width is auto-calculated based on text length. *)
2323+type width = private int
2424+2525+(** Fractional scaling numerator (0-15).
2626+2727+ Used together with denominator for precise fractional scaling. *)
2828+type numerator = private int
2929+3030+(** Fractional scaling denominator (0-15).
3131+3232+ Used together with numerator for precise fractional scaling. *)
3333+type denominator = private int
3434+3535+(** Vertical alignment for fractionally scaled text.
3636+ - [Bottom]: Align to bottom (0)
3737+ - [Center]: Align to center (1)
3838+ - [Top]: Align to top (2) *)
3939+type vertical_align =
4040+ | Bottom
4141+ | Center
4242+ | Top
4343+4444+(** Horizontal alignment for fractionally scaled text.
4545+ - [Left]: Align to left (0)
4646+ - [Center]: Align to center (1)
4747+ - [Right]: Align to right (2) *)
4848+type horizontal_align =
4949+ | Left
5050+ | Center
5151+ | Right
5252+5353+(** Metadata for text sizing. *)
5454+type metadata = {
5555+ scale : scale option;
5656+ width : width option;
5757+ numerator : numerator option;
5858+ denominator : denominator option;
5959+ vertical : vertical_align option;
6060+ horizontal : horizontal_align option;
6161+}
6262+6363+(** {1 Constructors} *)
6464+6565+(** [make_scale n] creates a scale value.
6666+ @raise Invalid_argument if [n] is not in range 1-7. *)
6767+val make_scale : int -> scale
6868+6969+(** [make_width n] creates a width value.
7070+ @raise Invalid_argument if [n] is not in range 0-7. *)
7171+val make_width : int -> width
7272+7373+(** [make_numerator n] creates a numerator value.
7474+ @raise Invalid_argument if [n] is not in range 0-15. *)
7575+val make_numerator : int -> numerator
7676+7777+(** [make_denominator n] creates a denominator value.
7878+ @raise Invalid_argument if [n] is not in range 0-15. *)
7979+val make_denominator : int -> denominator
8080+8181+(** {1 Metadata Creation} *)
8282+8383+(** Empty metadata (all fields set to [None]). *)
8484+val empty : metadata
8585+8686+(** [with_scale s metadata] sets the scale. *)
8787+val with_scale : scale -> metadata -> metadata
8888+8989+(** [with_width w metadata] sets the width. *)
9090+val with_width : width -> metadata -> metadata
9191+9292+(** [with_fraction num den metadata] sets fractional scaling. *)
9393+val with_fraction : numerator -> denominator -> metadata -> metadata
9494+9595+(** [with_vertical_align v metadata] sets vertical alignment. *)
9696+val with_vertical_align : vertical_align -> metadata -> metadata
9797+9898+(** [with_horizontal_align h metadata] sets horizontal alignment. *)
9999+val with_horizontal_align : horizontal_align -> metadata -> metadata
100100+101101+(** {1 Escape Sequence Generation} *)
102102+103103+(** [render metadata text] generates the complete escape sequence for sized text.
104104+105105+ @param metadata The sizing metadata
106106+ @param text The text to render (max 4096 bytes of UTF-8)
107107+ @return The escape sequence string
108108+ @raise Invalid_argument if text exceeds 4096 bytes *)
109109+val render : metadata -> string -> string
110110+111111+(** [render_to_channel oc metadata text] writes the escape sequence to a channel.
112112+113113+ @param oc Output channel
114114+ @param metadata The sizing metadata
115115+ @param text The text to render (max 4096 bytes of UTF-8)
116116+ @raise Invalid_argument if text exceeds 4096 bytes *)
117117+val render_to_channel : out_channel -> metadata -> string -> unit
118118+119119+(** {1 Convenience Functions} *)
120120+121121+(** [double text] renders text at double size (scale=2). *)
122122+val double : string -> string
123123+124124+(** [triple text] renders text at triple size (scale=3). *)
125125+val triple : string -> string
126126+127127+(** [quadruple text] renders text at quadruple size (scale=4). *)
128128+val quadruple : string -> string
129129+130130+(** [half text] renders text at half size (n=1, d=2). *)
131131+val half : string -> string
132132+133133+(** [superscript text] renders text as superscript (n=1, d=2, v=Top). *)
134134+val superscript : string -> string
135135+136136+(** [subscript text] renders text as subscript (n=1, d=2, v=Bottom). *)
137137+val subscript : string -> string
138138+139139+(** [scaled n text] renders text at scale [n] (1-7).
140140+ @raise Invalid_argument if [n] is not in range 1-7. *)
141141+val scaled : int -> string -> string