Wrap paragraph at N characters

A hilariously simple task, but I actually can’t figure out how to do this with the Format module.

What is the absolute simplest way to print a string that is a paragraph of text, wrapped at say 80 characters, without using any third-party libraries?

Someone might post a more idiomatic version but in the meantime:

# Format.printf "%a%a" Format.pp_set_margin 30 Format.pp_print_text "What is the absolute simplest way to print a string that is a paragraph of text, wrapped at say 80 characters, without using any third-party libraries?\n";;
What is the absolute simplest
way to print a string that is
a paragraph of text, wrapped
at say 80 characters, without
using any third-party
libraries?
- : unit = ()
5 Likes

Format is not a very good option for rendering with absolute ascii width constraint requirement (which is not something that you want to do for human text rendering, since the number of bytes is a bad approximation for the graphical width).

For a quick approximation, you can use Format.pp_print_text that will insert a break hint on ascii spaces. You can also replace this function by one which insert a break on each character:

let text_with_break ppf s =
  String.iter (Format.fprintf ppf "%c@,") s

Foregoing Format entirely, you could use:

let print_with_byte_limit lim s =
    String.fold_left (fun counter c ->
      let counter =
        if c = '\n' then 0
        else if counter = lim then
          (print_newline (); 1)
        else counter + 1
      in
      print_char c; counter
     ) 0 s

Finally, you can also combine this idea with the Format module by enforcing the character limit in the low-level format device with:

let rec splitted lim counter newline f n =
  if !counter + n < lim then begin
    f n;
    counter := !counter + n
  end
  else
    let d = lim - !counter in
    f d;
    newline ();
    splitted lim counter newline f (n-d)

let with_limit lim ppf =
  let counter = ref 0 in
  let fns = Format.pp_get_formatter_out_functions ppf () in
  let out_newline () = counter := 0; fns.out_newline () in
  let out_string s start len =
    for i = start to start + len - 1 do
      let c = s.[i] in
      begin
        if c = '\n' then counter := 0
        else if !counter >= lim then (out_newline (); incr counter)
        else incr counter
      end;
      fns.out_string s i 1
    done
  in
  let out_spaces = splitted lim counter out_newline fns.out_spaces in
  let out_indent = splitted lim counter out_newline fns.out_indent in
  Format.pp_set_formatter_out_functions ppf
    { Format.out_indent;
      out_string;
      out_newline;
      out_spaces;
      out_flush = fns.out_flush
    }
4 Likes

Thank you! That solves it quite nicely. Learnt a bit about the so-called ‘compositionality’ as well from your answer.

Thanks, I appreciate the depth you’ve gone to. Going to be honest, I’m completely new to OCaml (but not programming in general), so much of what you’ve written is beyond me right now. But I’ve bookmarked this question so that I may refer to it in the future when I’m further along.