Skip to content

Commit cbde192

Browse files
committed
feat: add a rich text API
rich text allows to mix styling within lines, across lines, etc.
1 parent 73c252c commit cbde192

File tree

4 files changed

+224
-60
lines changed

4 files changed

+224
-60
lines changed

src/PrintBox.ml

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ end
3333

3434
type view =
3535
| Empty
36-
| Text of {
37-
l: string list;
38-
style: Style.t;
39-
}
36+
| Text of rich_text
4037
| Frame of t
4138
| Pad of position * t (* vertical and horizontal padding *)
4239
| Align of {
@@ -51,21 +48,71 @@ type view =
5148
inner: t;
5249
}
5350

51+
and rich_text =
52+
| RT_str of string
53+
| RT_style of Style.t * rich_text
54+
| RT_cat of rich_text list
55+
5456
and t = view
5557

58+
module Rich_text = struct
59+
type t = rich_text
60+
61+
type view = rich_text =
62+
| RT_str of string
63+
| RT_style of Style.t * rich_text
64+
| RT_cat of rich_text list
65+
66+
let[@inline] view (self:t) : view = self
67+
68+
let line_ s : t = RT_str s
69+
let line s : t =
70+
if String.contains s '\n' then invalid_arg "PrintBox.Rich_text.line";
71+
line_ s
72+
73+
let with_style style s : t = RT_style (style, s)
74+
let bold s = with_style Style.bold s
75+
let newline : t = RT_str "\n"
76+
let space : t = RT_str " "
77+
78+
let cat l : t = match l with
79+
| [] -> RT_str ""
80+
| [x] -> x
81+
| _ -> RT_cat l
82+
83+
let cat_with ~sep l =
84+
let rec loop acc = function
85+
| [] -> assert (acc=[]); RT_str ""
86+
| [x] -> cat (List.rev (x::acc))
87+
| x :: tl -> loop (sep :: x :: acc) tl
88+
in
89+
loop [] l
90+
91+
let lines l = cat_with ~sep:newline l
92+
let lines_text l = lines @@ List.rev @@ List.rev_map line l
93+
94+
let text s : t = RT_str s
95+
96+
let sprintf fmt =
97+
let buffer = Buffer.create 64 in
98+
Printf.kbprintf (fun _ -> text (Buffer.contents buffer)) buffer fmt
99+
let asprintf fmt = Format.kasprintf text fmt
100+
101+
let s = text
102+
end
103+
56104
let empty = Empty
57105
let[@inline] view (t:t) : view = t
58106

59-
let[@inline] line_ s = Text {l=[s]; style=Style.default}
60-
61-
let line_with_style style s =
62-
if String.contains s '\n' then invalid_arg "PrintBox.line";
63-
Text {l=[s]; style}
107+
(* no check for \n *)
108+
let[@inline] line_ s = Text (Rich_text.line_ s)
64109

65-
let line s = line_with_style Style.default s
110+
let[@inline] line s = Text (Rich_text.line s)
111+
let[@inline] line_with_style style str = Text (Rich_text.(with_style style @@ line str))
66112

67-
let text s = Text {l=[s]; style=Style.default}
68-
let text_with_style style s = Text {l=[s]; style}
113+
let rich_text t : t = Text t
114+
let text s = Text (Rich_text.text s)
115+
let text_with_style style str = Text Rich_text.(with_style style @@ text str)
69116

70117
let sprintf_with_style style format =
71118
let buffer = Buffer.create 64 in
@@ -78,8 +125,9 @@ let sprintf format = sprintf_with_style Style.default format
78125
let asprintf format = Format.kasprintf text format
79126
let asprintf_with_style style format = Format.kasprintf (text_with_style style) format
80127

81-
let[@inline] lines l = Text {l; style=Style.default}
82-
let[@inline] lines_with_style style l = Text {l; style}
128+
let[@inline] lines l = Text (Rich_text.lines_text l)
129+
let[@inline] lines_with_style style l =
130+
Text Rich_text.(with_style style @@ lines_text l)
83131

84132
let int x = line_ (string_of_int x)
85133
let float x = line_ (string_of_float x)

src/PrintBox.mli

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ type t
102102
(** Main type for a document composed of nested boxes.
103103
@since 0.2 the type [t] is opaque *)
104104

105+
type rich_text
106+
(** Text with formatting and new lines.
107+
@since NEXT_RELEASE *)
108+
105109
(** The type [view] can be used to observe the inside of the box,
106110
now that [t] is opaque.
107111
@@ -110,10 +114,7 @@ type t
110114
*)
111115
type view = private
112116
| Empty
113-
| Text of {
114-
l: string list;
115-
style: Style.t;
116-
}
117+
| Text of rich_text
117118
| Frame of t
118119
| Pad of position * t (* vertical and horizontal padding *)
119120
| Align of {
@@ -157,10 +158,16 @@ val lines : string list -> t
157158
[lines l] is the same as [text (String.concat "\n" l)]. *)
158159

159160
val int_ : int -> t
161+
(** @deprecated use {!int} *)
162+
[@@deprecated "use int"]
160163

161164
val bool_ : bool -> t
165+
(** @deprecated use {!bool} *)
166+
[@@deprecated "use bool"]
162167

163168
val float_ : float -> t
169+
(** @deprecated use {!float} *)
170+
[@@deprecated "use float"]
164171

165172
val int : int -> t
166173
(** @since 0.2 *)
@@ -171,6 +178,10 @@ val bool : bool -> t
171178
val float : float -> t
172179
(** @since 0.2 *)
173180

181+
val rich_text : rich_text -> t
182+
(** A box containing rich text. See {!Rich_text} for more.
183+
@since NEXT_RELEASE *)
184+
174185
val frame : t -> t
175186
(** Put a single frame around the box *)
176187

@@ -240,14 +251,14 @@ val init_grid : ?bars:bool ->
240251
line:int -> col:int -> (line:int -> col:int -> t) -> t
241252
(** Same as {!grid} but takes the matrix as a function *)
242253

243-
val grid_l :
254+
val grid_l :
244255
?pad:(t -> t) ->
245256
?bars:bool ->
246257
t list list -> t
247258
(** Same as {!grid} but from lists.
248259
@since 0.3 *)
249260

250-
val grid_text_l :
261+
val grid_text_l :
251262
?pad:(t -> t) ->
252263
?bars:bool ->
253264
string list list -> t
@@ -338,11 +349,66 @@ val asprintf_with_style : Style.t -> ('a, Format.formatter, unit, t) format4 ->
338349
(** Formatting for {!text}, with style.
339350
@since 0.3 *)
340351

352+
(** Rich text *)
353+
module Rich_text : sig
354+
type t = rich_text
355+
356+
(** View on the internals of the rich text.
357+
{b NOTE} this is unstable for now, no promise of stability is made. *)
358+
type view = private
359+
| RT_str of string
360+
| RT_style of Style.t * t
361+
| RT_cat of t list
362+
363+
val view : t -> view
364+
365+
val s : string -> t
366+
(** Short for {!text} *)
367+
368+
val line : string -> t
369+
(** Make a single-line text object.
370+
@raise Invalid_argument if the string contains ['\n'] *)
371+
372+
val text : string -> t
373+
(** Any text, possibly with several lines *)
374+
375+
val space : t
376+
377+
val newline : t
378+
379+
val cat : t list -> t
380+
(** [cat txts] is the concatenation of items in [txts]. *)
381+
382+
val cat_with : sep:t -> t list -> t
383+
(** [concat_with ~sep l] concatenates items of [l],
384+
inserting [sep] in between each. It doesn't add [sep] after
385+
the last element. *)
386+
387+
val lines : t list -> t
388+
(** Concatenate with interspersed new lines *)
389+
390+
val lines_text : string list -> t
391+
(** same as [lines @@ List.map line l] *)
392+
393+
val sprintf : ('a, Buffer.t, unit, t) format4 -> 'a
394+
(** Formatting. *)
395+
396+
val asprintf : ('a, Format.formatter, unit, t) format4 -> 'a
397+
(** Formatting. *)
398+
399+
val with_style : Style.t -> t -> t
400+
(** Add style to the text. *)
401+
402+
val bold : t -> t
403+
(** Short for [with_style Style.bold] *)
404+
end
405+
341406
(** {2 Simple Structural Interface} *)
342407

343408
type 'a ktree = unit -> [`Nil | `Node of 'a * 'a ktree list]
344409
type box = t
345410

411+
(** A simple interface. *)
346412
module Simple : sig
347413
type t =
348414
[ `Empty

src/printbox-html/PrintBox_html.ml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,20 @@ let rec to_html_rec ~config (b: B.t) : [< Html_types.flow5 > `Div `Ul `Table `P]
8686
let to_html_rec = to_html_rec ~config in
8787
match B.view b with
8888
| B.Empty -> H.div []
89-
| B.Text {l; style} ->
90-
let a, bold = attrs_of_style style in
91-
let l = List.map H.txt l in
92-
let l = if bold then List.map (fun x->H.b [x]) l else l in
93-
H.div
94-
~a:(H.a_class config.cls_text :: (a @ config.a_text))
95-
l
89+
| B.Text rt ->
90+
let module RT = B.Rich_text in
91+
let rec conv_rt style rt =
92+
match RT.view rt with
93+
| RT.RT_str s ->
94+
let a, bold = attrs_of_style style in
95+
let s = H.txt s in
96+
let s = if bold then H.b [s] else s in
97+
H.div [s]
98+
~a:(H.a_class config.cls_text :: (a @ config.a_text))
99+
| RT.RT_cat l -> H.div (List.map (conv_rt style) l)
100+
| RT.RT_style (style, sub) -> conv_rt style sub
101+
in
102+
conv_rt B.Style.default rt
96103
| B.Pad (_, b)
97104
| B.Frame b -> to_html_rec b
98105
| B.Align {h=`Right;inner=b;v=_} ->

0 commit comments

Comments
 (0)