File 2161-Introduce-bfwrite-and-bformat-functions.patch of Package erlang

From 0705d7ed868d54209b5c92efc6a2aeb72a1efe6d Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Wed, 30 Apr 2025 09:03:33 +0200
Subject: [PATCH 1/2] Introduce bfwrite and bformat functions

This can be used to directly create utf8 binaries instead of iolists.

Slightly slower for some usage input but the result uses a lot less
memory on 64b systems.
---
 lib/stdlib/src/io_lib.erl        | 366 ++++++++++++++++-
 lib/stdlib/src/io_lib_format.erl | 407 ++++++++++++++++---
 lib/stdlib/src/io_lib_pretty.erl | 663 +++++++++++++++++++++++++------
 lib/stdlib/test/io_SUITE.erl     | 201 +++++++---
 4 files changed, 1393 insertions(+), 244 deletions(-)

diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 550142680d..1528b49c8e 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -72,13 +72,17 @@ used for flattening deep lists.
 -compile(nowarn_deprecated_catch).
 
 -export([fwrite/2,fwrite/3,fread/2,fread/3,format/2,format/3]).
+-export([bfwrite/2, bfwrite/3, bformat/2, bformat/3]).
 -export([scan_format/2,unscan_format/1,build_text/1,build_text/2]).
 -export([print/1,print/4,indentation/2]).
 
--export([write/1,write/2,write/3,nl/0,format_prompt/1,format_prompt/2]).
+-export([write/1,write/2,write/3,write/5,bwrite/2]).
+-export([nl/0,format_prompt/1,format_prompt/2]).
 -export([write_binary/3]).
 -export([write_atom/1,write_string/1,write_string/2,write_latin1_string/1,
-         write_latin1_string/2, write_char/1, write_latin1_char/1]).
+         write_latin1_string/2, write_char/1, write_latin1_char/1,
+         bwrite_string/3
+        ]).
 
 -export([write_atom_as_latin1/1, write_string_as_latin1/1,
          write_string_as_latin1/2, write_char_as_latin1/1]).
@@ -100,9 +104,16 @@ used for flattening deep lists.
 
 -export([chars_length/1]).
 
+-export([write_bin/5, write_string_bin/3, write_binary_bin/4]).
+
 -export_type([chars/0, latin1_string/0, continuation/0,
               fread_error/0, fread_item/0, format_spec/0, chars_limit/0]).
 
+-dialyzer([{nowarn_function,
+            [string_bin_escape_unicode/6,
+             string_bin_escape_latin1/6]},
+           no_improper_lists]).
+
 %%----------------------------------------------------------------------
 
 -doc "An possibly deep list containing only `t:char/0`s.".
@@ -207,6 +218,32 @@ Valid option:
 fwrite(Format, Args, Options) ->
     format(Format, Args, Options).
 
+
+-doc(#{equiv => bfwrite(Format, Data, [])}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bfwrite(Format, Data) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()].
+
+bfwrite(F, A) ->
+    bformat(F, A, []).
+
+-doc("""
+   Binary variant of `fwrite/3`
+
+   Returns a UTF-8 encoded binary string.
+   """).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bfwrite(Format, Data, Options) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()],
+      Options :: [Option],
+      Option :: {'chars_limit', CharsLimit},
+      CharsLimit :: chars_limit().
+
+bfwrite(F, A, Opts) ->
+    bformat(F, A, Opts).
+
 -doc """
 Tries to read `String` in accordance with the control sequences in `Format`.
 
@@ -234,6 +271,7 @@ _Example:_
 {ok,[15.6,1.73e-5,24.5],[]}
 ```
 """.
+
 -spec fread(Format, String) -> Result when
       Format :: string(),
       String :: string(),
@@ -316,6 +354,38 @@ format(Format, Args, Options) ->
             erlang:error(badarg, [Format, Args])
     end.
 
+
+-doc(#{equiv => bfwrite(Format, Data, [])}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bformat(Format, Data) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()].
+
+bformat(Format, Args) ->
+    try io_lib_format:fwrite_bin(Format, Args)
+    catch
+        C:R:S ->
+            test_modules_loaded(C, R, S),
+            erlang:error(badarg, [Format, Args])
+    end.
+
+-doc(#{equiv => bfwrite(Format, Data, Options)}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bformat(Format, Data, Options) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()],
+      Options :: [Option],
+      Option :: {'chars_limit', CharsLimit},
+      CharsLimit :: chars_limit().
+
+bformat(Format, Args, Options) ->
+    try io_lib_format:fwrite_bin(Format, Args, Options)
+    catch
+        C:R:S ->
+            test_modules_loaded(C, R, S),
+            erlang:error(badarg, [Format, Args])
+    end.
+
 -doc """
 Returns a list corresponding to the specified format string, where control
 sequences have been replaced with corresponding tuples. This list can be passed
@@ -513,7 +583,8 @@ _Example:_
       Options :: [Option],
       Option :: {'chars_limit', CharsLimit}
               | {'depth', Depth}
-              | {'encoding', 'latin1' | 'utf8' | 'unicode'},
+              | {'encoding', 'latin1' | 'utf8' | 'unicode'}
+              | {'maps_order', maps:iterator_order()},
       CharsLimit :: chars_limit(),
       Depth :: depth().
 
@@ -522,6 +593,12 @@ write(Term, Options) when is_list(Options) ->
     Encoding = get_option(encoding, Options, epp:default_encoding()),
     CharsLimit = get_option(chars_limit, Options, -1),
     MapsOrder = get_option(maps_order, Options, undefined),
+    write(Term, Depth, Encoding, MapsOrder, CharsLimit);
+write(Term, Depth) ->
+    write(Term, [{depth, Depth}, {encoding, latin1}]).
+
+-doc false.
+write(Term, Depth, Encoding, MapsOrder, CharsLimit) ->
     if
         Depth =:= 0; CharsLimit =:= 0 ->
             "...";
@@ -532,29 +609,195 @@ write(Term, Options) when is_list(Options) ->
             If = io_lib_pretty:intermediate
                  (Term, Depth, CharsLimit, RecDefFun, Encoding, _Str=false, MapsOrder),
             io_lib_pretty:write(If)
+    end.
+
+-doc """
+Behaves as `write/2` but returns a UTF-8 encoded binary string.
+""".
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bwrite(Term, Options) -> unicode:unicode_binary() when
+      Term :: term(),
+      Options :: [Option],
+      Option :: {'chars_limit', CharsLimit}
+              | {'depth', Depth}
+              | {'encoding', 'latin1' | 'utf8' | 'unicode'}
+              | {'maps_order', maps:iterator_order()},
+      CharsLimit :: chars_limit(),
+      Depth :: depth().
+
+bwrite(Term, Options) when is_list(Options) ->
+    Depth = get_option(depth, Options, -1),
+    %% InEncoding = How should we display atoms
+    InEncoding = get_option(encoding, Options, epp:default_encoding()),
+    CharsLimit = get_option(chars_limit, Options, -1),
+    MapsOrder = get_option(maps_order, Options, undefined),
+    {S, _Sz} = write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit),
+    S.
+
+-doc false.
+-spec write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit) ->
+          {unicode:unicode_binary(), Sz::integer()} when
+      Term :: term(),
+      Depth :: depth(),
+      InEncoding :: 'latin1' | 'utf8' | 'unicode',
+      MapsOrder :: maps:iterator_order() | undefined,
+      CharsLimit :: chars_limit().
+
+write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit) ->
+    if
+        Depth =:= 0; CharsLimit =:= 0 ->
+            {<<"...">>, 3};
+        is_integer(CharsLimit), CharsLimit < 0, is_integer(Depth) ->
+            write_bin1(Term, Depth, InEncoding, MapsOrder, 0, <<>>);
+        is_integer(CharsLimit), CharsLimit > 0 ->
+            RecDefFun = fun(_, _) -> no end,
+            If = io_lib_pretty:intermediate(Term, Depth, CharsLimit, RecDefFun,
+                                            {InEncoding, utf8}, _Str=false, MapsOrder),
+            {_, Len, _, _} = If,
+            Bin = io_lib_pretty:write(If, {unicode,utf8}),
+            {Bin, Len}
+    end.
+
+write_bin1(_Term, 0, _Enc, _O, Sz, Acc) ->
+    {<<Acc/binary, "...">>, Sz+3};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_integer(Term) ->
+    Int = integer_to_binary(Term),
+    {<<Acc/binary, Int/binary>>, byte_size(Int) + Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_float(Term) ->
+    Float = float_to_binary(Term, [short]),
+    {<<Acc/binary, Float/binary>>, byte_size(Float)+Sz};
+write_bin1(Atom, _D, latin1, _O, Sz, Acc) when is_atom(Atom) ->
+    Str = unicode:characters_to_binary(write_atom_as_latin1(Atom)),
+    {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Atom, _D, _Enc, _O, Sz, Acc) when is_atom(Atom) ->
+    Str = write_atom(Atom),
+    {<<Acc/binary, (unicode:characters_to_binary(Str))/binary>>, length(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_port(Term) ->
+    Str = (list_to_binary(erlang:port_to_list(Term))),
+    {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_pid(Term) ->
+    Str = (list_to_binary(pid_to_list(Term))),
+    {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_reference(Term) ->
+    Str = (list_to_binary(erlang:ref_to_list(Term))),
+    {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(<<_/bitstring>>=Term, D, _Enc, _O, Sz, Acc) ->
+    write_binary_bin0(Term, D, Sz, Acc);
+write_bin1([], _D, _Enc, _O, Sz, Acc) ->
+    {<<Acc/binary, "[]">>, Sz+2};
+write_bin1({}, _D, _Enc, _O, Sz, Acc) ->
+    {<<Acc/binary, "{}">>, Sz+2};
+write_bin1(F, _D, _Enc, _O, Sz, Acc) when is_function(F) ->
+    Str = (list_to_binary(erlang:fun_to_list(F))),
+    {<<Acc/binary, Str/binary>>, byte_size(Str) + Sz};
+write_bin1([H|T], D, Enc, O, Sz, Acc) ->
+    if
+	D =:= 1 -> {<<Acc/binary, "[...]">>, Sz+5};
+	true ->
+            {Head, Sz1} = write_bin1(H, D-1, Enc, O, Sz+1, <<Acc/binary, $[>>),
+            write_tail_bin(T, D-1, Enc, O, Sz1, Head)
     end;
-write(Term, Depth) ->
-    write(Term, [{depth, Depth}, {encoding, latin1}]).
+write_bin1(T, D, Enc, O, Sz, Acc) when is_tuple(T) ->
+    if
+	D =:= 1 -> {<<Acc/binary, "{...}">>, Sz+5};
+	true ->
+            {First, Sz1} = write_bin1(element(1, T), D-1, Enc, O, Sz+1, <<Acc/binary, ${>>),
+            write_tuple_bin(T, 2, D-1, Enc, O, Sz1, First)
+    end;
+write_bin1(Term, 1, _Enc, _O, Sz, Acc) when is_map(Term) ->
+    {<<Acc/binary, "#{}">>, Sz+3};
+write_bin1(Map, D, Enc, O, Sz, Acc) when is_map(Map), is_integer(D) ->
+    I = maps:iterator(Map, O),
+    case maps:next(I) of
+        {K, V, NextI} ->
+            D0 = D - 1,
+            {Start,Sz1} = write_map_assoc_bin(K, V, D0, Enc, O, Sz+2, <<Acc/binary, $#, ${>>),
+            write_map_body_bin(NextI, D0, D0, Enc, O, Sz1, Start);
+        none ->
+            {~"#{}", 3}
+    end.
+
+write_tail_bin([], _D, _Enc, _O, Sz, Acc) -> {<<Acc/binary, $]>>, Sz+1};
+write_tail_bin(_, 1, _Enc, _O, Sz, Acc) -> {<<Acc/binary, "|...]">>, Sz+1};
+write_tail_bin([H|T], D, Enc, O, Sz, Acc) ->
+    {Head, Sz1} = write_bin1(H, D-1, Enc, O, Sz+1, <<Acc/binary,$,>>),
+    write_tail_bin(T, D-1, Enc, O, Sz1, Head);
+write_tail_bin(Other, D, Enc, O, Sz, Acc) ->
+    {Tail, Sz1} = write_bin1(Other, D-1, Enc, O, Sz+1, <<Acc/binary, $|>>),
+    {<<Tail/binary, $]>>, Sz1+1}.
+
+write_tuple_bin(T, I, _D, _Enc, _O, Sz, Acc) when I > tuple_size(T) ->
+    {<<Acc/binary, $}>>, Sz+1};
+write_tuple_bin(_, _I, 1, _Enc, _O, Sz, Acc) ->
+    {<<Acc/binary, ",...}">>, Sz+4};
+write_tuple_bin(T, I, D, Enc, O, Sz, Acc) ->
+    {Elem, Sz1} = write_bin1(element(I, T), D-1, Enc, O, Sz+1, <<Acc/binary, $,>>),
+    write_tuple_bin(T, I+1, D-1, Enc, O, Sz1, Elem).
+
+write_map_body_bin(_, 1, _D0, _Enc, _O, Sz, Acc) ->
+    {<<Acc/binary, ",...}">>, Sz+5};
+write_map_body_bin(I, D, D0, Enc, O, Sz, Acc) ->
+    case maps:next(I) of
+        {K, V, NextI} ->
+            {W, Sz1} = write_map_assoc_bin(K, V, D0, Enc, O, Sz+1, <<Acc/binary, $,>>),
+            write_map_body_bin(NextI, D - 1, D0, Enc, O, Sz1, W);
+        none ->
+            {<<Acc/binary, "}">>, Sz+1}
+    end.
+write_map_assoc_bin(K, V, D, Enc, O, Sz, Acc) ->
+    {KBin, Sz1} = write_bin1(K, D, Enc, O, Sz, Acc),
+    write_bin1(V, D, Enc, O, Sz1 + 4, <<KBin/binary, " => ">>).
+
+write_binary_bin0(B, D, Sz, Acc) ->
+    {S, _} = write_binary_bin(B, D, -1, Acc),
+    {S, byte_size(S)+Sz}.
+
+-doc false.
+-spec write_binary_bin(Bin, Depth, T, Acc) -> {unicode:unicode_binary(), binary()} when
+      Bin :: binary(),
+      Depth :: integer(),
+      T :: integer(),
+      Acc :: unicode:unicode_binary().
+
+write_binary_bin(B, D, T, Acc) when is_integer(T) ->
+    write_binary_body_bin(B, D, tsub(T, 4), <<Acc/binary, "<<" >>).
+
+write_binary_body_bin(<<>> = B, _D, _T, Acc) ->
+    {<<Acc/binary, ">>" >>, B};
+write_binary_body_bin(<<_/bitstring>>=B, D, T, Acc) when D =:= 1; T =:= 0->
+    {<<Acc/binary, "...>>">>, B};
+write_binary_body_bin(<<X:8>>, _D, _T, Acc) ->
+    {<<Acc/binary, (integer_to_binary(X))/binary, ">>">>, <<>>};
+write_binary_body_bin(<<X:8,Rest/bitstring>>, D, T, Acc) ->
+    IntBin = integer_to_binary(X),
+    write_binary_body_bin(Rest, D-1, tsub(T, byte_size(IntBin) + 1),
+                          <<Acc/binary, IntBin/binary, $,>>);
+write_binary_body_bin(B, _D, _T, Acc) ->
+    L = bit_size(B),
+    <<X:L>> = B,
+    {<<Acc/binary, (integer_to_binary(X))/binary, $:,
+       (integer_to_binary(L))/binary,">>">>,
+     <<>>}.
+
 
 write1(_Term, 0, _E, _O) -> "...";
 write1(Term, _D, _E, _O) when is_integer(Term) -> integer_to_list(Term);
 write1(Term, _D, _E, _O) when is_float(Term) -> io_lib_format:fwrite_g(Term);
 write1(Atom, _D, latin1, _O) when is_atom(Atom) -> write_atom_as_latin1(Atom);
 write1(Atom, _D, _E, _O) when is_atom(Atom) -> write_atom(Atom);
-write1(Term, _D, _E, _O) when is_port(Term) -> write_port(Term);
+write1(Term, _D, _E, _O) when is_port(Term) -> erlang:port_to_list(Term);
 write1(Term, _D, _E, _O) when is_pid(Term) -> pid_to_list(Term);
-write1(Term, _D, _E, _O) when is_reference(Term) -> write_ref(Term);
+write1(Term, _D, _E, _O) when is_reference(Term) -> erlang:ref_to_list(Term);
 write1(<<_/bitstring>>=Term, D, _E, _O) -> write_binary(Term, D);
 write1([], _D, _E, _O) -> "[]";
 write1({}, _D, _E, _O) -> "{}";
+write1(F, _D, _E, _O) when is_function(F) -> erlang:fun_to_list(F);
 write1([H|T], D, E, O) ->
     if
 	D =:= 1 -> "[...]";
 	true ->
 	    [$[,[write1(H, D-1, E, O)|write_tail(T, D-1, E, O)],$]]
     end;
-write1(F, _D, _E, _O) when is_function(F) ->
-    erlang:fun_to_list(F);
 write1(Term, D, E, O) when is_map(Term) ->
     write_map(Term, D, E, O);
 write1(T, D, E, O) when is_tuple(T) ->
@@ -581,12 +824,6 @@ write_tuple(_, _I, 1, _E, _O) -> [$, | "..."];
 write_tuple(T, I, D, E, O) ->
     [$,,write1(element(I, T), D-1, E, O)|write_tuple(T, I+1, D-1, E, O)].
 
-write_port(Port) ->
-    erlang:port_to_list(Port).
-
-write_ref(Ref) ->
-    erlang:ref_to_list(Ref).
-
 write_map(_, 1, _E, _O) -> "#{}";
 write_map(Map, D, E, O) when is_integer(D) ->
     I = maps:iterator(Map, O),
@@ -738,6 +975,105 @@ write_string(S) ->
 write_string(S, Q) ->
     [Q|write_string1(unicode_as_unicode, S, Q)].
 
+-doc "Returns the UTF-8 encoded binary `String` surrounded by `Qoute`.".
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bwrite_string(String, Qoute, InEnc) -> unicode:unicode_binary() when
+      String :: string() | binary(),
+      Qoute  :: integer() | [],
+      InEnc  :: 'unicode' | 'latin1'.  %% In case of binary input
+
+bwrite_string(S, Q, InEnc) ->
+    {Bin, _Sz} = write_string_bin(S, Q, InEnc),
+    Bin.
+
+-doc false.
+-spec write_string_bin(String, Qoute, InEnc) -> {unicode:unicode_binary(), Sz::integer()} when
+      String :: string() | binary(),
+      Qoute  :: integer() | [],
+      InEnc  :: 'unicode' | 'latin1'.  %% In case of binary input
+
+write_string_bin(S, Q, _InEnc) when is_list(S) ->
+    Escaped = write_string(S,Q),
+    Sz = chars_length(Escaped),
+    Bin = unicode:characters_to_binary(Escaped),
+    true = is_binary(Bin),
+    {Bin, Sz};
+write_string_bin(S, Q, latin1) when is_binary(S) ->
+    Escaped = string_bin_escape_latin1(S, S, Q, [], 0, 0),
+    Sz = iolist_size(Escaped) + if Q == [] -> 0; true -> 2 end,
+    Bin = unicode:characters_to_binary([Q,Escaped,Q], latin1, utf8),
+    true = is_binary(Bin),
+    {Bin, Sz};
+write_string_bin(S, Q, unicode) when is_binary(S) ->
+    Escaped = string_bin_escape_unicode(S, S, Q, [], 0, 0),
+    Bin = case Q of
+              [] when is_binary(Escaped) -> Escaped;
+              _ -> unicode:characters_to_binary([Q,Escaped,Q])
+          end,
+    {Bin, string:length(Bin)}.
+
+string_bin_escape_latin1(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) ->
+    case needs_escape(Byte, Q) of
+        false ->
+            string_bin_escape_latin1(Rest, Orig, Q, Acc, Skip0, Len+1);
+        Escape when Len =:= 0 ->
+            Skip = Skip0 + Len + 1,
+            string_bin_escape_latin1(Rest, Orig, Q, [Acc | Escape], Skip, 0);
+        Escape ->
+            Skip = Skip0 + Len + 1,
+            Part = binary_part(Orig, Skip0, Len),
+            string_bin_escape_latin1(Rest, Orig, Q, [Acc, Part | Escape], Skip, 0)
+    end;
+string_bin_escape_latin1(_, Orig, _, _Acc, 0, _) ->
+    Orig;
+string_bin_escape_latin1(_, Orig, _, Acc, Skip, Len) ->
+    case Len =:= 0 of
+        true -> Acc;
+        false -> [Acc | binary_part(Orig, Skip, Len)]
+    end.
+
+string_bin_escape_unicode(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) when Byte > 127 ->
+    string_bin_escape_unicode(Rest, Orig, Q, Acc, Skip0, Len+1);
+string_bin_escape_unicode(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) ->
+    case needs_escape(Byte, Q) of
+        false ->
+            string_bin_escape_unicode(Rest, Orig, Q, Acc, Skip0, Len+1);
+        Escape when Len =:= 0 ->
+            Skip = Skip0 + Len + 1,
+            string_bin_escape_unicode(Rest, Orig, Q, [Acc | Escape], Skip, 0);
+        Escape ->
+            Skip = Skip0 + Len + 1,
+            Part = binary_part(Orig, Skip0, Len),
+            string_bin_escape_unicode(Rest, Orig, Q, [Acc, Part | Escape], Skip, 0)
+    end;
+string_bin_escape_unicode(_, Orig, _, _Acc, 0, _) ->
+    Orig;
+string_bin_escape_unicode(_, Orig, _, Acc, Skip, Len) ->
+    case Len =:= 0 of
+        true -> Acc;
+        false -> [Acc | binary_part(Orig, Skip, Len)]
+    end.
+
+needs_escape(Q, Q)   -> [$\\, Q];
+needs_escape($\\, _) -> [$\\, $\\];
+needs_escape(C, _) when C >= $\s,   C =< $~ ->
+    false;
+needs_escape($\n, _) -> [$\\, $n];
+needs_escape($\r, _) -> [$\\, $r];
+needs_escape($\t, _) -> [$\\, $t];
+needs_escape($\v, _) -> [$\\, $v];
+needs_escape($\b, _) -> [$\\, $b];
+needs_escape($\f, _) -> [$\\, $f];
+needs_escape($\e, _) -> [$\\, $e];
+needs_escape($\d, _) -> [$\\, $d];
+needs_escape(C, _) when C >= $\240, C =< $\377 ->
+    false;
+needs_escape(C, _) when C < $\240 ->
+    C1 = (C bsr 6) + $0,
+    C2 = ((C bsr 3) band 7) + $0,
+    C3 = (C band 7) + $0,
+    [$\\,C1,C2,C3].
+
 %% Backwards compatibility.
 -doc false.
 write_unicode_string(S) ->
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index 6e552a3503..8eb8b9af14 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -22,10 +22,17 @@
 
 -compile(nowarn_deprecated_catch).
 
+-dialyzer([{nowarn_function, [iolist_to_bin/4]},
+           no_improper_lists]).
+
 %% Formatting functions of io library.
 
--export([fwrite/2,fwrite/3,fwrite_g/1,indentation/2,scan/2,unscan/1,
-         build/1, build/2]).
+-export([fwrite/2,fwrite/3,
+         fwrite_bin/2, fwrite_bin/3,
+         fwrite_g/1,
+         indentation/2,
+         scan/2,unscan/1,
+         build/1, build/2, build_bin/1, build_bin/2]).
 
 %%  Format the arguments in Args after string Format. Just generate
 %%  an error if there is an error in the arguments.
@@ -58,6 +65,24 @@ fwrite(Format, Args) ->
 fwrite(Format, Args, Options) ->
     build(scan(Format, Args), Options).
 
+%% Binary variants
+-spec fwrite_bin(Format, Data) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()].
+
+fwrite_bin(Format, Args) ->
+    build_bin(scan(Format, Args)).
+
+-spec fwrite_bin(Format, Data, Options) -> unicode:unicode_binary() when
+      Format :: io:format(),
+      Data :: [term()],
+      Options :: [Option],
+      Option :: {'chars_limit', CharsLimit},
+      CharsLimit :: io_lib:chars_limit().
+
+fwrite_bin(Format, Args, Options) ->
+    build_bin(scan(Format, Args), Options).
+
 %% Build the output text for a pre-parsed format list.
 
 -spec build(FormatList) -> io_lib:chars() when
@@ -84,6 +109,32 @@ build(Cs, Options) ->
             build_limited(Res1, P, NumOfLimited, RemainingChars, 0)
     end.
 
+%% binary
+
+-spec build_bin(FormatList) -> unicode:unicode_binary() when
+      FormatList :: [char() | io_lib:format_spec()].
+build_bin(Cs) ->
+    build_bin(Cs, []).
+
+-spec build_bin(FormatList, Options) -> unicode:unicode_binary() when
+      FormatList :: [char() | io_lib:format_spec()],
+      Options :: [Option],
+      Option :: {'chars_limit', CharsLimit},
+      CharsLimit :: io_lib:chars_limit().
+
+build_bin(Cs, Options) ->
+    CharsLimit = get_option(chars_limit, Options, -1),
+    Res1 = build_small_bin(Cs),
+    {P, S, W, Other} = count_small(Res1),
+    case P + S + W of
+        0 ->
+            iolist_to_binary(Res1);
+        NumOfLimited ->
+            RemainingChars = sub(CharsLimit, Other),
+            Res = build_limited_bin(Res1, P, NumOfLimited, RemainingChars, 0),
+            iolist_to_binary(Res)  %% Res only contains utf-8 binary or ASCII
+    end.
+
 %% Parse all control sequences in the format string.
 
 -spec scan(Format, Data) -> FormatList when
@@ -264,9 +315,12 @@ count_small([#{control_char := $W}|Cs], #{w := W} = Cnts) ->
     count_small(Cs, Cnts#{w := W + 1});
 count_small([#{control_char := $s}|Cs], #{w := W} = Cnts) ->
     count_small(Cs, Cnts#{w := W + 1});
-count_small([S|Cs], #{other := Other} = Cnts) when is_list(S);
-                                                   is_binary(S) ->
+count_small([S|Cs], #{other := Other} = Cnts)
+  when is_list(S) ->
     count_small(Cs, Cnts#{other := Other + io_lib:chars_length(S)});
+count_small([S|Cs], #{other := Other} = Cnts)
+  when is_binary(S) ->
+    count_small(Cs, Cnts#{other := Other + string:length(S)});
 count_small([C|Cs], #{other := Other} = Cnts) when is_integer(C) ->
     count_small(Cs, Cnts#{other := Other + 1});
 count_small([], #{p := P, s := S, w := W, other := Other}) ->
@@ -310,9 +364,10 @@ build_limited([#{control_char := C, args := As, width := F, adjust := Ad,
                      sub(MaxLen0, Len)
              end,
     if
-	NumOfPs > 0 -> [S|build_limited(Cs, NumOfPs, Count,
-                                        MaxLen, indentation(S, I))];
-	true -> [S|build_limited(Cs, NumOfPs, Count, MaxLen, I)]
+	NumOfPs > 0 ->
+            [S|build_limited(Cs, NumOfPs, Count, MaxLen, indentation(S, I))];
+	true ->
+            [S|build_limited(Cs, NumOfPs, Count, MaxLen, I)]
     end;
 build_limited([$\n|Cs], NumOfPs, Count, MaxLen, _I) ->
     [$\n|build_limited(Cs, NumOfPs, Count, MaxLen, 0)];
@@ -326,20 +381,95 @@ decr_pc($p, Pc) -> Pc - 1;
 decr_pc($P, Pc) -> Pc - 1;
 decr_pc(_, Pc) -> Pc.
 
+build_small_bin([#{control_char := C, args := As, width := F, adjust := Ad,
+                   precision := P, pad_char := Pad, encoding := Enc}=CC | Cs]) ->
+    case control_small(C, As, F, Ad, P, Pad, Enc) of
+        not_small ->
+            [CC | build_small_bin(Cs)];
+        [$\n|_] = NL ->
+            [NL | build_small_bin(Cs)];
+        S ->
+            SBin = unicode:characters_to_binary(S, Enc, unicode),
+            true = is_binary(SBin),
+            [SBin | build_small_bin(Cs)]
+    end;
+build_small_bin([$\t|Cs]) ->
+    [$\t | build_small_bin(Cs)];
+build_small_bin([C|Cs]) ->
+    [C | build_small_bin(Cs)];
+build_small_bin([]) ->
+    [].
+
+build_limited_bin([#{control_char := C, args := As, width := F, adjust := Ad,
+                     precision := P, pad_char := Pad, encoding := Enc,
+                     strings := Str} = Map | Cs],
+                  NumOfPs0, Count0, MaxLen0, I0) ->
+    Ord = maps:get(maps_order, Map, undefined),
+    MaxChars = if
+                   MaxLen0 < 0 -> MaxLen0;
+                   true -> MaxLen0 div Count0
+               end,
+    {S, Sz, I} = control_limited_bin(C, As, F, Ad, P, Pad, Enc, Str, Ord, MaxChars, I0),
+    NumOfPs = decr_pc(C, NumOfPs0),
+    Count = Count0 - 1,
+    MaxLen = if
+                 MaxLen0 < 0 -> MaxLen0; % optimization
+                 Sz < 0 -> sub(MaxLen0, string:length(S));
+                 true -> sub(MaxLen0, Sz)
+             end,
+    if
+	NumOfPs > 0, I < 0 ->
+            [S|build_limited_bin(Cs, NumOfPs, Count, MaxLen, indentation(S, I0))];
+	true ->
+            [S|build_limited_bin(Cs, NumOfPs, Count, MaxLen, I)]
+    end;
+build_limited_bin([[$\n|_]=NL|Cs], NumOfPs, Count, MaxLen, _I) ->
+    [NL|build_limited_bin(Cs, NumOfPs, Count, MaxLen, 0)];
+build_limited_bin([$\t|Cs], NumOfPs, Count, MaxLen, I) ->
+    [$\t|build_limited_bin(Cs, NumOfPs, Count, MaxLen, ((I + 8) div 8) * 8)];
+build_limited_bin([C|Cs], NumOfPs, Count, MaxLen, I) when is_integer(C) ->
+    [C|build_limited_bin(Cs, NumOfPs, Count, MaxLen, 1+I)];
+build_limited_bin([Bin|Cs], NumOfPs, Count, MaxLen, I) when is_binary(Bin) ->
+    [Bin|build_limited_bin(Cs, NumOfPs, Count, MaxLen, byte_size(Bin)+I)];
+build_limited_bin([], _, _, _, _) -> [].
+
+
 %%  Calculate the indentation of the end of a string given its start
 %%  indentation. We assume tabs at 8 cols.
 
 -spec indentation(String, StartIndent) -> integer() when
-      String :: io_lib:chars(),
+      String :: unicode:chardata(),
       StartIndent :: integer().
 
-indentation([$\n|Cs], _I) -> indentation(Cs, 0);
-indentation([$\t|Cs], I) -> indentation(Cs, ((I + 8) div 8) * 8);
+indentation([$\n|Cs], _I) ->
+    indentation(Cs, 0);
+indentation([$\t|Cs], I) ->
+    indentation(Cs, ((I + 8) div 8) * 8);
 indentation([C|Cs], I) when is_integer(C) ->
     indentation(Cs, I+1);
 indentation([C|Cs], I) ->
     indentation(Cs, indentation(C, I));
-indentation([], I) -> I.
+indentation(Bin, I0) when is_binary(Bin) ->
+    indentation_bin(Bin, I0);
+indentation([], I) ->
+    I.
+
+indentation_bin(Bin, I) ->
+    indentation_bin(Bin, Bin, 0, 0, I).
+
+indentation_bin(<<$\n, Cs/binary>>, Orig, _Start, N,_I) ->
+    indentation_bin(Cs, Orig, N+1, 0, 0);
+indentation_bin(<<$\t, Cs/binary>>, Orig, Start, N, I0) ->
+    Part = binary:part(Orig, Start, N),
+    PSz = string:length(Part),
+    indentation_bin(Cs, Orig, N+1, N+1, ((I0+PSz + 8) div 8) * 8);
+indentation_bin(<<_, Cs/binary>>, Orig, Start, N, I) ->
+    indentation_bin(Cs, Orig, Start, N+1, I);
+indentation_bin(<<>>, Orig, Start, N, I) ->
+    Part = binary:part(Orig, Start, N),
+    PSz = string:length(Part),
+    I + PSz.
+
 
 %% control_small(FormatChar, [Argument], FieldWidth, Adjust, Precision,
 %%               PadChar, Encoding) -> String
@@ -391,34 +521,55 @@ control_small($n, [], F, Adj, P, Pad, _Enc) -> newline(F, Adj, P, Pad);
 control_small($i, [_A], _F, _Adj, _P, _Pad, _Enc) -> [];
 control_small(_C, _As, _F, _Adj, _P, _Pad, _Enc) -> not_small.
 
-control_limited($s, [L0], F, Adj, P, Pad, latin1=Enc, _Str, _Ord, CL, _I) ->
-    L = iolist_to_chars(L0, F, CL),
-    string(L, limit_field(F, CL), Adj, P, Pad, Enc);
-control_limited($s, [L0], F, Adj, P, Pad, unicode=Enc, _Str, _Ord, CL, _I) ->
-    L = cdata_to_chars(L0, F, CL),
-    uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc));
+control_limited($s, [L0], F, Adj, P, Pad, Enc, _Str, _Ord, CL, _I) ->
+    if Enc =:= latin1 ->
+            L = iolist_to_chars(L0, F, CL),
+            string(L, limit_field(F, CL), Adj, P, Pad, Enc);
+       Enc =:= unicode ->
+            L = cdata_to_chars(L0, F, CL),
+            uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc))
+    end;
 control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I) ->
-    Chars = io_lib:write(A, [
-        {depth, -1},
-        {encoding, Enc},
-        {chars_limit, CL},
-        {maps_order, Ord}
-    ]),
-    term(Chars, F, Adj, P, Pad);
+    Chars = io_lib:write(A, -1, Enc, Ord, CL),
+    term(Chars, F, Adj, P, Pad, Enc);
 control_limited($p, [A], F, Adj, P, Pad, Enc, Str, Ord, CL, I) ->
-    print(A, -1, F, Adj, P, Pad, Enc, Str, Ord, CL, I);
+    print(A, -1, F, Adj, P, Pad, Enc, list, Str, Ord, CL, I);
 control_limited($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I)
-           when is_integer(Depth) ->
-    Chars = io_lib:write(A, [
-        {depth, Depth},
-        {encoding, Enc},
-        {chars_limit, CL},
-        {maps_order, Ord}
-    ]),
-    term(Chars, F, Adj, P, Pad);
+  when is_integer(Depth) ->
+    Chars = io_lib:write(A, Depth, Enc, Ord, CL),
+    term(Chars, F, Adj, P, Pad, Enc);
 control_limited($P, [A,Depth], F, Adj, P, Pad, Enc, Str, Ord, CL, I)
-           when is_integer(Depth) ->
-    print(A, Depth, F, Adj, P, Pad, Enc, Str, Ord, CL, I).
+  when is_integer(Depth) ->
+    print(A, Depth, F, Adj, P, Pad, Enc, list, Str, Ord, CL, I).
+
+control_limited_bin($s, [L0], F, Adj, P, Pad, Enc, _Str, _Ord, CL, _I) ->
+    {B, Sz} = iolist_to_bin(L0, F, CL, Enc),
+    string_bin(B, Sz, limit_field(F, CL), Adj, P, Pad, Enc);
+control_limited_bin($w, [A], F, Adj, P, Pad, Enc, _Str, Ord, CL, I) ->
+    {Chars, Sz} = io_lib:write_bin(A, -1, Enc, Ord, CL),
+    term_bin(Chars, F, Adj, P, Pad, Enc, Sz, I);
+control_limited_bin($p, [A], F, Adj, P, Pad, Enc, Str, Ord, CL, I) ->
+    print(A, -1, F, Adj, P, Pad, Enc, binary, Str, Ord, CL, I);
+control_limited_bin($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, Ord, CL, I)
+  when is_integer(Depth) ->
+    {Chars, Sz} = io_lib:write_bin(A, Depth, Enc, Ord, CL),
+    term_bin(Chars, F, Adj, P, Pad, Enc, Sz, I);
+control_limited_bin($P, [A,Depth], F, Adj, P, Pad, Enc, Str, Ord, CL, I)
+  when is_integer(Depth) ->
+    print(A, Depth, F, Adj, P, Pad, Enc, binary, Str, Ord, CL, I).
+
+term_bin(T, none, _Adj, none, _Pad, _Enc, Sz, I) ->
+    {T, Sz, Sz+I};
+term_bin(T, none, Adj, P, Pad, Enc, Sz, I) ->
+    term_bin(T, P, Adj, P, Pad, Enc, Sz, I);
+term_bin(T, F, Adj, P0, Pad, _Enc, Sz, I) ->
+    P = erlang:min(Sz, case P0 of none -> F; _ -> min(P0, F) end),
+    if
+	Sz > P ->
+	    {adjust(chars($*, P), chars(Pad, F-P), Adj), F, I+F};
+	F >= P ->
+            {adjust(T, chars(Pad, F-Sz), Adj), F, I+F}
+    end.
 
 -ifdef(UNICODE_AS_BINARIES).
 uniconv(C) ->
@@ -438,10 +589,15 @@ base(B) when is_integer(B) ->
 %%  Adjust the characters within the field if length less than Max padding
 %%  with PadChar.
 
-term(T, none, _Adj, none, _Pad) -> T;
-term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
-term(T, F, Adj, P0, Pad) ->
-    L = io_lib:chars_length(T),
+term(T, none, _Adj, none, _Pad, _Enc) ->
+    T;
+term(T, none, Adj, P, Pad, Enc) ->
+    term(T, P, Adj, P, Pad, Enc);
+term(T, F, Adj, P0, Pad, Enc) ->
+    L = case Enc =:= latin1 of
+            true  -> io_lib:chars_length(T);
+            false -> string:length(T)
+        end,
     P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end),
     if
 	L > P ->
@@ -455,19 +611,34 @@ term(T, F, Adj, P0, Pad) ->
 %% Print a term. Field width sets maximum line length, Precision sets
 %% initial indentation.
 
-print(T, D, none, Adj, P, Pad, E, Str, Ord, ChLim, I) ->
-    print(T, D, 80, Adj, P, Pad, E, Str, Ord, ChLim, I);
-print(T, D, F, Adj, none, Pad, E, Str, Ord, ChLim, I) ->
-    print(T, D, F, Adj, I+1, Pad, E, Str, Ord, ChLim, I);
-print(T, D, F, right, P, _Pad, Enc, Str, Ord, ChLim, _I) ->
+print(T, D, none, Adj, P, Pad, E, Type, Str, Ord, ChLim, I) ->
+    print(T, D, 80, Adj, P, Pad, E, Type, Str, Ord, ChLim, I);
+print(T, D, F, Adj, none, Pad, E, Type, Str, Ord, ChLim, I) ->
+    print(T, D, F, Adj, I+1, Pad, E, Type, Str, Ord, ChLim, I);
+print(T, D, F, right, P, _Pad, Enc, list, Str, Ord, ChLim, _I) ->
     Options = [{chars_limit, ChLim},
                {column, P},
                {line_length, F},
                {depth, D},
                {encoding, Enc},
                {strings, Str},
-               {maps_order, Ord}],
-    io_lib_pretty:print(T, Options).
+               {maps_order, Ord}
+              ],
+    io_lib_pretty:print(T, Options);
+print(T, D, F, right, P, _Pad, Enc, binary, Str, Ord, ChLim, I) ->
+    Options = #{chars_limit => ChLim,
+                column => P,
+                line_length => F,
+                depth => D,
+                encoding => Enc,
+                strings => Str,
+                maps_order => Ord
+               },
+    {Bin, Sz, Col} = Res = io_lib_pretty:print_bin(T, Options),
+    case Col > 0 of
+        true  -> Res;
+        false -> {Bin, Sz, I - Col}
+    end.
 
 %% fwrite_e(Float, Field, Adjust, Precision, PadChar)
 
@@ -478,7 +649,7 @@ fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
 fwrite_e(Fl, F, Adj, none, Pad) ->
     fwrite_e(Fl, F, Adj, 6, Pad);
 fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
-    term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
+    term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad, latin1).
 
 float_e(Fl, Fd, P) ->
     signbit(Fl) ++ abs_float_e(abs(Fl), Fd, P).
@@ -533,7 +704,7 @@ fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
 fwrite_f(Fl, F, Adj, none, Pad) ->
     fwrite_f(Fl, F, Adj, 6, Pad);
 fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
-    term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad).
+    term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad, latin1).
 
 float_f(Fl, Fd, P) ->
     signbit(Fl) ++ abs_float_f(abs(Fl), Fd, P).
@@ -678,6 +849,89 @@ limit_cdata_to_chars(Cs, Limit, Mode) ->
             [GC | limit_cdata_to_chars(Cs1, Limit - 1, Mode)]
     end.
 
+iolist_to_bin(L, F, CharsLimit, latin1) when CharsLimit < 0; CharsLimit >= F ->
+    Bin = unicode:characters_to_binary(L, latin1, unicode),
+    {Bin, iolist_size(L)};
+iolist_to_bin(L, F, CharsLimit, unicode) when CharsLimit < 0; CharsLimit >= F ->
+    case unicode:characters_to_binary(L) of
+        Bin when is_binary(Bin) ->
+            {Bin, undefined};
+        {error, Ok, Bad} ->
+            %% Try latin1, strange allowing mixing latin1 and utf8
+            %% but we handled it before for unknown reason.
+            {Bin, _} = iolist_to_bin(Bad, F, CharsLimit, latin1),
+            {iolist_to_binary([Ok|Bin]), undefined}
+    end;
+iolist_to_bin(L, _, CharsLimit, Enc) ->
+    {Acc, Sz, _Limit, Rest} = limit_iolist_to_bin(L, sub(CharsLimit, 3), Enc, 0, <<>>),
+    case string:is_empty(Rest) of
+        true ->
+            {Acc, Sz};
+        false ->
+            {Cont, Size, _, _} = limit_iolist_to_bin(Rest, 4, Enc, 0, <<>>),
+            if Size < 4 ->
+                    {<<Acc/binary, Cont/binary>>, Sz+Size};
+               true ->
+                    {<<Acc/binary, "...">>, Sz+3}
+            end
+    end.
+
+limit_iolist_to_bin(Cs, 0, _, Size, Acc) ->
+    {Acc, Size, 0, Cs};
+limit_iolist_to_bin([C|Cs], Limit, latin1, Size, Acc)
+  when C >= $\000, C =< $\377 ->
+    limit_iolist_to_bin(Cs, Limit-1, latin1, Size+1, <<Acc/binary, C/utf8>>);
+limit_iolist_to_bin(Bin0, Limit, latin1, Size0, Acc)
+  when is_binary(Bin0) ->
+    case byte_size(Bin0) of
+        Sz when Sz > Limit ->
+            {B1, B2} = split_binary(Bin0, Limit),
+            Bin = unicode:characters_to_binary(B1, latin1, unicode),
+            {<<Acc/binary, Bin/binary>>, Size0+Limit, 0, B2};
+        Sz ->
+            Bin = unicode:characters_to_binary(Bin0, latin1, unicode),
+            {<<Acc/binary, Bin/binary>>, Size0+Sz, Limit-Sz, []}
+    end;
+limit_iolist_to_bin(Bin0, Limit, unicode, Size0, Acc)
+  when is_binary(Bin0) ->
+    try string:length(Bin0) of
+        Sz when Sz > Limit ->
+            B1 = string:slice(Bin0, 0, Limit),
+            Skip = byte_size(Bin0) - byte_size(B1),
+            <<_:Skip/binary, B2/binary>> = Bin0,
+            {<<Acc/binary, B1/binary>>, Size0+Limit, 0, B2};
+        Sz ->
+            {<<Acc/binary, Bin0/binary>>, Size0+Sz, Limit-Sz, []}
+    catch _:_ ->  %% We allow latin1 as binary strings, so try that
+            limit_iolist_to_bin(Bin0, Limit, latin1, Size0, Acc)
+    end;
+limit_iolist_to_bin(CPs, Limit, unicode, Size, Acc) ->
+    case string:next_grapheme(CPs) of
+        {error, <<C,Cs1/binary>>} ->
+            %% This is how ~ts handles Latin1 binaries with option
+            %% chars_limit.
+            limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, C/utf8>>);
+        {error, [C|Cs1]} -> % not all versions of module string return this
+            limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, C/utf8>>);
+        [] ->
+            {Acc, Size, Limit, []};
+        [GC|Cs1] when is_integer(GC) ->
+            limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, GC/utf8>>);
+        [GC|Cs1] ->
+            Utf8 = unicode:characters_to_binary(GC),
+            limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, Utf8/binary>>)
+    end;
+limit_iolist_to_bin([Deep|Cs], Limit0, Enc, Size0, Acc0) ->
+    {Acc, Sz, L, Cont} = limit_iolist_to_bin(Deep, Limit0, Enc, Size0, Acc0),
+    case string:is_empty(Cont) of
+        true ->
+            limit_iolist_to_bin(Cs, L, Enc, Sz, Acc);
+        false ->
+            limit_iolist_to_bin([Cont|Cs], L, Enc, Sz, Acc)
+    end;
+limit_iolist_to_bin([], Limit, _Enc, Size, Acc) ->
+    {Acc, Size, Limit, []}.
+
 limit_field(F, CharsLimit) when CharsLimit < 0; F =:= none ->
     F;
 limit_field(F, CharsLimit) ->
@@ -685,7 +939,8 @@ limit_field(F, CharsLimit) ->
 
 %% string(String, Field, Adjust, Precision, PadChar)
 
-string(S, none, _Adj, none, _Pad, _Enc) -> S;
+string(S, none, _Adj, none, _Pad, _Enc) ->
+    S;
 string(S, F, Adj, none, Pad, Enc) ->
     string_field(S, F, Adj, io_lib:chars_length(S), Pad, Enc);
 string(S, none, _Adj, P, Pad, Enc) ->
@@ -711,6 +966,41 @@ string_field(S, F, Adj, N, Pad, _Enc) when N < F ->
 string_field(S, _, _, _, _, _) -> % N == F
     S.
 
+string_bin(S, _, none, _Adj, none, _Pad, _Enc) ->
+    {S, -1, -1};
+string_bin(S, undefined, F, Adj, P, Pad, Enc) ->
+    unicode = Enc, %% Assert size=-1 should only happen for unicode
+    string_bin(S, string:length(S), F, Adj, P, Pad, Enc);
+string_bin(S, Sz, F, Adj, none, Pad, Enc) ->
+    string_field_bin(S, F, Adj, Sz, Pad, Enc);
+string_bin(S, Sz, none, _Adj, P, Pad, Enc) ->
+    string_field_bin(S, P, left, Sz, Pad, Enc);
+string_bin(S0, Sz, F, Adj, P, Pad, Enc) when F >= P ->
+    if F > P ->
+	    if Sz > P ->
+                    S = adjust(flat_trunc(S0, P, Enc), chars(Pad, F-P), Adj),
+                    {S, F, -1};
+	       Sz < P ->
+		    S = adjust([S0|chars(Pad, P-Sz)], chars(Pad, F-P), Adj),
+                    {S, F, -1};
+	       true -> % N == P
+		    S = adjust(S0, chars(Pad, F-P), Adj),
+                    {S, Sz+(F-P), -1}
+	    end;
+       true -> % F == P
+	    string_field_bin(S0, F, Adj, Sz, Pad, Enc)
+    end.
+
+string_field_bin(S0, F, _Adj, N, _Pad, Enc) when N > F ->
+    S = flat_trunc(S0, F, Enc),
+    {S, F, -1};
+string_field_bin(S0, F, Adj, N, Pad, _Enc) when N < F ->
+    S = adjust(S0, chars(Pad, F-N), Adj),
+    {S, N+F-N, -1};
+string_field_bin(S, _, _, N, _, _) -> % N == F
+    {S, N, -1}.
+
+
 %% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
 %% -> [Char].
 
@@ -718,10 +1008,10 @@ unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
   when Base >= 2, Base =< 1+$Z-$A+10 ->
     if Int < 0 ->
 	    S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
-	    term([$-|S], F, Adj, none, Pad);
+	    term([$-|S], F, Adj, none, Pad, latin1);
        true ->
 	    S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
-	    term(S, F, Adj, none, Pad)
+	    term(S, F, Adj, none, Pad, latin1)
     end.
 
 %% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
@@ -731,10 +1021,10 @@ prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
   when Base >= 2, Base =< 1+$Z-$A+10 ->
     if Int < 0 ->
 	    S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
-	    term([$-,Prefix|S], F, Adj, none, Pad);
+	    term([$-,Prefix|S], F, Adj, none, Pad, latin1);
        true ->
 	    S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
-	    term([Prefix|S], F, Adj, none, Pad)
+	    term([Prefix|S], F, Adj, none, Pad, latin1)
     end.
 
 %% char(Char, Field, Adjust, Precision, PadChar) -> chars().
@@ -760,11 +1050,14 @@ adjust(Data, Pad, right) -> [Pad|Data].
 
 %% Flatten and truncate a deep list to at most N elements.
 
-flat_trunc(List, N, latin1) when is_integer(N), N >= 0 ->
+flat_trunc(List, N, latin1) when is_list(List), is_integer(N), N >= 0 ->
     {S, _} = lists:split(N, lists:flatten(List)),
     S;
-flat_trunc(List, N, unicode) when is_integer(N), N >= 0 ->
-    string:slice(List, 0, N).
+flat_trunc(Str, N, unicode) when is_integer(N), N >= 0 ->
+    string:slice(Str, 0, N);
+flat_trunc(Bin, N, latin1) when is_binary(Bin), is_integer(N), N >= 0 ->
+    {B, _} = split_binary(Bin, N),
+    B.
 
 %% A deep version of lists:duplicate/2
 
@@ -800,7 +1093,7 @@ lowercase([H|T]) ->
 lowercase([]) ->
     [].
 
-%% Make sure T does change sign.
+%% Make sure T does not change sign.
 sub(T, _) when T < 0 -> T;
 sub(T, E) when T >= E -> T - E;
 sub(_, _) -> 0.
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index ced5ab1cb1..76490806e3 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -25,10 +25,11 @@
 %%% In this module "print" means the formatted printing while "write"
 %%% means just writing out onto one line.
 
--export([print/1,print/2,print/3,print/4,print/5,print/6]).
+-export([print/1,print/2,print/3,print/4,print/5,print/6,
+         print_bin/2]).
 
 %% To be used by io_lib only.
--export([intermediate/7, write/1]).
+-export([intermediate/7, write/1, write/2]).
 
 %%%
 %%% Exported functions
@@ -68,6 +69,23 @@ print(Term) ->
                 | {'strings', boolean()}
                 | {'maps_order', maps:iterator_order()}.
 -type options() :: [option()].
+-type options_map() :: map().
+
+-spec print_bin(term(), options_map()) -> {unicode:unicode_binary(), Sz::integer(), Col::integer()}.
+print_bin(Term, Options) when is_map(Options) ->
+    Col = maps:get(column, Options, 1),
+    Ll = maps:get(line_length, Options, 80),
+    D = maps:get(depth, Options, -1),
+    M = maps:get(line_max_chars, Options, -1),
+    T = maps:get(chars_limit, Options, -1),
+    RecDefFun = maps:get(record_print_fun, Options, no_fun),
+    InEncoding = maps:get(encoding, Options, epp:default_encoding()),
+    Strings = maps:get(strings, Options, true),
+    MapsOrder = maps:get(maps_order, Options, undefined),
+    print_bin(Term, Col, Ll, D, M, T, RecDefFun, {InEncoding, utf8}, Strings, MapsOrder);
+print_bin(Term, Options) when is_list(Options) ->
+    print_bin(Term, maps:from_list(Options)).
+
 
 -spec print(term(), rec_print_fun()) -> chars();
            (term(), options()) -> chars().
@@ -116,15 +134,15 @@ print(Term, Col, Ll, D, M, RecDefFun) ->
 %% M = CHAR_MAX (-1 if no max, 60 when printing from shell)
 print(_, _, _, 0, _M, _T, _RF, _Enc, _Str, _Ord) -> "...";
 print(_, _, _, _D, _M, 0, _RF, _Enc, _Str, _Ord) -> "...";
-print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord) when Col =< 0 ->
+print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord)
+  when Col =< 0 ->
     %% ensure Col is at least 1
     print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str, Ord);
-print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str, _Ord) when is_atom(Atom) ->
+print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str, _Ord)
+  when is_atom(Atom) ->
     write_atom(Atom, Enc);
-print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord) when is_tuple(Term);
-                                                         is_list(Term);
-                                                         is_map(Term);
-                                                         is_bitstring(Term) ->
+print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord)
+  when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
     %% preprocess and compute total number of chars
     {_, Len, _Dots, _} = If =
         case T < 0 of
@@ -135,14 +153,14 @@ print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord) when is_tuple(Term);
     M = max_cs(M0, Len),
     if
         Ll =:= 0 ->
-            write(If);
+            write(If, Enc);
         Len < Ll - Col, Len =< M ->
             %% write the whole thing on a single line when there is room
-            write(If);
+            write(If, Enc);
         true ->
             %% compute the indentation TInd for tagged tuples and records
-            TInd = while_fail([-1, 4], 
-                              fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end, 
+            TInd = while_fail([-1, 4],
+                              fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
                               1),
             pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
     end;
@@ -150,6 +168,52 @@ print(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str, _Ord) ->
     %% atomic data types (bignums, atoms, ...) are never truncated
     io_lib:write(Term).
 
+print_bin(_, _Col, _, 0, _M, _T, _RF, _Enc, _Str, _Ord) ->
+    {~"...", 3, -3};
+print_bin(_, _Col, _, _D, _M, 0, _RF, _Enc, _Str, _Ord) ->
+    {~"...", 3, -3};
+print_bin(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord)
+  when Col =< 0 ->
+    %% ensure Col is at least 1
+    print_bin(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str, Ord);
+print_bin(Atom, _Col, _Ll, _D, _M, _T, _RF, {InEnc, _}, _Str, _Ord)
+  when is_atom(Atom) ->
+    {Bin, Sz} = io_lib:write_bin(Atom, -1, InEnc, undefined, -1),
+    {Bin, Sz, -Sz};
+print_bin(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord)
+  when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
+    %% preprocess and compute total number of chars
+    {_, Len, _Dots, _} = If =
+        case T < 0 of
+            true -> print_length(Term, D, T, RecDefFun, Enc, Str, Ord);
+            false -> intermediate(Term, D, T, RecDefFun, Enc, Str, Ord)
+        end,
+    %% use Len as CHAR_MAX if M0 = -1
+    M = max_cs(M0, Len),
+    if
+        Ll =:= 0 ->
+            {write_bin(If, <<>>), Len, -Len};
+        Len < Ll - Col, Len =< M ->
+            %% write the whole thing on a single line when there is room
+            {write_bin(If,<<>>), Len, -Len};
+        true ->
+            %% compute the indentation TInd for tagged tuples and records
+            TInd = while_fail([-1, 4],
+                              fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
+                              1),
+            {Bin, NL, W} = pp_bin(If, Col, Ll, M, TInd, indent(Col), 0, 0, 0, 0, <<>>),
+            case NL > 0 of
+                true ->
+                    {Bin, Len+Col*NL, W-1};
+                false ->
+                    {Bin, Len, -W}
+            end
+    end;
+print_bin(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str, _Ord) ->
+    %% atomic data types (bignums, atoms, ...) are never truncated
+    {Bin, Sz} = io_lib:write_bin(Term, -1, unicode, undefined, -1),
+    {Bin, Sz, -Sz}.
+
 %%%
 %%% Local functions
 %%%
@@ -160,7 +224,7 @@ max_cs(M, Len) when M < 0 ->
 max_cs(M, _Len) ->
     M.
 
--define(ATM(T), is_list(element(1, T))).
+-define(ATM(T), (is_list(element(1, T)) orelse is_binary(element(1, T)))).
 -define(ATM_PAIR(Pair),
         ?ATM(element(2, element(1, Pair))) % Key
         andalso
@@ -332,9 +396,9 @@ pp_tail([{_, Len, _, _}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, W) ->
 pp_tail({dots, _, _, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, _W) ->
     [S | "..."];
 pp_tail({_, Len, _, _}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, W)
-                  when Len + 1 < Ll - Col - (LD + 1), 
-                       Len + 1 + W + (LD + 1) =< M, 
-                       ?ATM(E) ->
+  when Len + 1 < Ll - Col - (LD + 1),
+       Len + 1 + W + (LD + 1) =< M,
+       ?ATM(E) ->
     [S | write(E)];
 pp_tail(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, _W) ->
     [S, $\n, Ind | pp(E, Col0, Ll, M, TInd, Ind, LD + 1, 0)].
@@ -369,7 +433,256 @@ pp_binary(S, N, _N0, Ind) ->
             S
     end.
 
+
+-define(IND(Spaces), (list_to_binary(Spaces))/binary).
+
+pp_bin({_S,Len,_,_} = If, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+  when Len < Ll - Col - LD, Len + W + LD =< M ->
+    {write_bin(If, Acc), NL, Pos+Len};
+pp_bin({{list,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    pp_list_bin(L, Col+1, Ll, M, TInd, indent(1, Ind), LD, $|, $], NL, W+1, Pos+1,
+                <<Acc/binary, $[>>);
+pp_bin({{tuple,true,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    pp_tag_tuple_bin(L, Col, Ll, M, TInd, Ind, LD, NL, W+1, Pos+1,
+                     <<Acc/binary, ${>>);
+pp_bin({{tuple,false,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    pp_list_bin(L, Col+1, Ll, M, TInd, indent(1, Ind), LD, $,, $}, NL, W+1, Pos+1,
+                <<Acc/binary, ${>>);
+pp_bin({{map,Pairs}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    pp_map_bin(Pairs, Col+2, Ll, M, TInd, indent(2, Ind), LD, NL, W+2, Pos,
+               <<Acc/binary, $#, ${>>);
+pp_bin({{record,[{Name0,NLen} | L]}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    Name = unicode:characters_to_binary(Name0),
+    pp_record_bin(L, NLen, Col, Ll, M, TInd, Ind, LD, NL, W+NLen+1, Pos+NLen+1,
+                  <<Acc/binary, Name/binary, ${>>);
+pp_bin({{bin,S}, _Len, _, _}, Col, Ll, M, _TInd, Ind, LD, NL, W, Pos, Acc) ->
+    pp_binary_bin(S, Col+2, Ll, M, indent(2, Ind), LD, NL, W, Pos, Acc);
+pp_bin({S,Len,_,_}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    Bin = unicode:characters_to_binary(S),
+    {<<Acc/binary, Bin/binary>>, NL, Pos+Len}.
+
+%%  Print a tagged tuple by indenting the rest of the elements
+%%  differently to the tag. Tuple has size >= 2.
+pp_tag_tuple_bin({dots, _, _, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, "...}">>, NL, Pos+4};
+pp_tag_tuple_bin([{Tag0,Tlen,_,_} | L], Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+    %% this uses TInd
+    TagInd = Tlen+2,
+    Tcol = Col+TagInd,
+    Tag = unicode:characters_to_binary(Tag0),
+    S = $,,
+    if
+        TInd > 0, TagInd > TInd ->
+            Col1 = Col+TInd,
+            Indent = indent(TInd, Ind),
+            pp_tail_bin(L, Col1, Tcol, Ll, M, TInd, Indent, LD, S, $}, NL, W+Tlen, Pos+Tlen,
+                        <<Acc/binary, Tag/binary>>);
+        true ->
+            Indent = indent(TagInd, Ind),
+            pp_list_bin(L, Tcol, Ll, M, TInd, Indent, LD, S, $}, NL, W+Tlen+1, Pos+Tlen+1,
+                        <<Acc/binary, Tag/binary, S>>)
+    end.
+
+pp_map_bin([], _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, $}>>, NL, Pos+1};                           % cannot happen
+pp_map_bin({dots, _, _, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, "...}">>, NL, Pos+4};                       % cannot happen
+pp_map_bin([P | Ps], Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+    {PS, NL, Pos, PW} = pp_pair_bin(P, Col, Ll, M, TInd, Ind, last_depth(Ps, LD), NL0, W, Pos0, Acc),
+    pp_pairs_tail_bin(Ps, Col, Col+PW, Ll, M, TInd, Ind, LD, NL, PW, Pos, PS).
+
+pp_pairs_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, $}>>, NL, Pos+1};
+pp_pairs_tail_bin({dots, _, _, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _L, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, ",...}">>, NL, Pos+5};
+pp_pairs_tail_bin([{_, Len, _, _}=P | Ps], Col0, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos, Acc) ->
+    LD1 = last_depth(Ps, LD),
+    ELen = 1+Len,
+    if
+        LD1 =:= 0, ELen+1 < Ll-Col, W+ELen+1 =< M, ?ATM_PAIR(P);
+        LD1 > 0, ELen < Ll-Col-LD1, W+ELen+LD1 =< M, ?ATM_PAIR(P) ->
+            pp_pairs_tail_bin(Ps, Col0, Col+ELen, Ll, M, TInd, Ind, LD, NL0, W+ELen, Pos+ELen,
+                              write_bin(P, <<Acc/binary, $,>>));
+        true ->
+            {PS, NL, Pos1, PW} = pp_pair_bin(P, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+                                             <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+            pp_pairs_tail_bin(Ps, Col0, Col0+PW, Ll, M, TInd, Ind, LD, NL, PW, Pos1, PS)
+    end.
+
+pp_pair_bin({_, Len, _, _}=Pair, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+  when Len < Ll - Col - LD, Len+W+LD =< M ->
+    {write_bin(Pair, Acc),
+     NL,
+     Len+Pos,
+     if
+         ?ATM_PAIR(Pair) ->
+             Len;
+         true ->
+             Ll % force nl
+     end};
+pp_pair_bin({{map_pair, K, V}, _Len, _, _}, Col0, Ll, M, TInd, Ind0, LD, NL0, W, Pos0, Acc0) ->
+    I = map_value_indent(TInd),
+    Ind = indent(I, Ind0),
+    {Acc1, NL1, _} = pp_bin(K, Col0, Ll, M, TInd, Ind0, LD, NL0, W, Pos0, Acc0),
+    {Acc2, NL, Pos} = pp_bin(V, Col0+I, Ll, M, TInd, Ind, LD, NL1+1, 0, Col0+I,
+                             <<Acc1/binary, " =>\n", ?IND(Ind)>>),
+    {Acc2, NL, Pos, Ll}.
+
+pp_record_bin([], _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, $}>>, NL, Pos+1};
+pp_record_bin({dots, _, _, _}, _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, "...}">>, NL, Pos+4};
+pp_record_bin([F | Fs], Nlen, Col0, Ll, M, TInd, Ind0, LD, NL0, W0, Pos0, Acc) ->
+    Nind = Nlen+1,
+    {Col, Ind, S, W} = rec_indent(Nind, TInd, Col0, Ind0, W0),
+    {Pos1, NL1} = if W == 0 -> {Col, NL0+1}; true -> {Pos0, NL0} end,
+    {FS, NL, Pos, FW} = pp_field_bin(F, Col, Ll, M, TInd, Ind, last_depth(Fs, LD), NL1, W, Pos1,
+                                     <<Acc/binary, ?IND(S)>>),
+    pp_fields_tail_bin(Fs, Col, Col+FW, Ll, M, TInd, Ind, LD, NL, W+FW, Pos, FS).
+
+pp_fields_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, $}>>, NL, Pos+1};
+pp_fields_tail_bin({dots, _, _ ,_}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, ",...}">>, NL, Pos+5};
+pp_fields_tail_bin([{_, Len, _, _}=F | Fs], Col0, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+    LD1 = last_depth(Fs, LD),
+    ELen = 1+Len,
+    if
+        LD1 =:= 0, ELen+1 < Ll-Col, W+ELen+1 =< M, ?ATM_FLD(F);
+        LD1 > 0, ELen < Ll-Col-LD1, W+ELen+LD1 =< M, ?ATM_FLD(F) ->
+            pp_fields_tail_bin(Fs, Col0, Col+ELen, Ll, M, TInd, Ind, LD, NL0, W+ELen, Pos0+ELen,
+                               write_field_bin(F, <<Acc/binary, $,>>));
+        true ->
+            {FS, NL, Pos, FW} = pp_field_bin(F, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+                                             <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+            pp_fields_tail_bin(Fs, Col0, Col0+FW, Ll, M, TInd, Ind, LD, NL, FW, Pos, FS)
+    end.
+
+pp_field_bin({_, Len, _, _}=Fl, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+  when Len < Ll-Col-LD, Len+W+LD =< M ->
+    {write_field_bin(Fl, Acc),
+     NL,
+     Len+Pos,
+     if
+         ?ATM_FLD(Fl) -> Len;
+         true -> Ll % force nl
+     end};
+pp_field_bin({{field, Name0, NameL, F},_,_, _}, Col0, Ll, M, TInd, Ind0, LD, NL0, W0, Pos0, Acc0) ->
+    {Col, Ind, S, W} = rec_indent(NameL, TInd, Col0, Ind0, W0+NameL),
+    Name = unicode:characters_to_binary(Name0),
+    {Acc, NL, Pos} =
+        case W of
+            0 ->
+                Acc1 = <<Acc0/binary, Name/binary, " =", ?IND(S)>>,
+                pp_bin(F, Col, Ll, M, TInd, Ind, LD, NL0+1, W, Col, Acc1);
+            _ ->
+                Acc1 = <<Acc0/binary, Name/binary, " = ", ?IND(S)>>,
+                pp_bin(F, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc1)
+        end,
+    {Acc, NL, Pos, Ll}. % force nl
+
+pp_list_bin({dots, _, _, _}, _Col0, _Ll, _M, _TInd, _Ind, _LD, _S, C, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, "...", C>>, NL, Pos+4};
+pp_list_bin([E | Es], Col0, Ll, M, TInd, Ind, LD, S, C, NL0, W, Pos0, Acc) ->
+    {ES, NL, Pos, WE} = pp_element_bin(E, Col0, Ll, M, TInd, Ind, last_depth(Es, LD), NL0, W, Pos0, Acc),
+    pp_tail_bin(Es, Col0, Col0+WE, Ll, M, TInd, Ind, LD, S, C, NL, W+WE, Pos, ES).
+
+pp_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _S, C, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, C>>, NL, Pos+1};
+pp_tail_bin([{_, Len, _, _}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, C, NL0, W, Pos0, Acc) ->
+    LD1 = last_depth(Es, LD),
+    ELen = 1+Len,
+    if
+        LD1 =:= 0, ELen+1 < Ll-Col, W+ELen+1 =< M, ?ATM(E);
+        LD1 > 0, ELen < Ll-Col-LD1, W+ELen+LD1 =< M, ?ATM(E) ->
+            pp_tail_bin(Es, Col0, Col+ELen, Ll, M, TInd, Ind, LD, S, C, NL0, W+ELen, Pos0+ELen,
+                        write_bin(E, <<Acc/binary, $,>>));
+        true ->
+            {ES, NL, Pos, WE} = pp_element_bin(E, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+                                               <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+            pp_tail_bin(Es, Col0, Col0+WE, Ll, M, TInd, Ind, LD, S, C, NL, WE, Pos, ES)
+    end;
+pp_tail_bin({dots, _, _, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, C, NL, _W, Pos, Acc) ->
+    {<<Acc/binary, S, "...", C>>, NL, Pos+5};
+pp_tail_bin({_, Len, _, _}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, C, NL, W, Pos, Acc)
+  when Len+1 < Ll - Col - (LD+1),
+       Len+1+W+(LD+1) =< M,
+       ?ATM(E) ->
+    Acc1 = write_bin(E, <<Acc/binary, S>>),
+    {<<Acc1/binary, C>>, NL, Pos+1+Len};
+pp_tail_bin(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, C, NL0, _W, _Pos0, Acc) ->
+    {Acc1, NL, Pos} = pp_bin(E, Col0, Ll, M, TInd, Ind, LD+1, NL0+1, 0, Col0,
+                             <<Acc/binary, S, $\n, ?IND(Ind)>>),
+    {<<Acc1/binary, C>>, NL, Pos+1}.
+
+pp_element_bin({_, Len, _, _}=E, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+  when Len < Ll - Col - LD, Len+W+LD =< M, ?ATM(E) ->
+    {write_bin(E, Acc), NL, Pos+Len, Len};
+pp_element_bin(E, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+    {Acc1, NL, Pos} = pp_bin(E, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc),
+    {Acc1, NL, Pos, Ll}. % force nl
+
+pp_binary_bin(Orig, Col, Ll, M, Ind, LD, NL, W, Pos, Acc) ->
+    N = erlang:max(8, erlang:min(Ll - Col, M - 4 - W) - LD),
+    <<$<,$<,Rest/binary>> = Orig,
+    pp_binary_bin_ind(Rest, Orig, Ind, 0, 2, N, N, NL, Pos, Acc).
+
+pp_binary_bin_ind(<<_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+    N1 = N-2,
+    case N1 < 0 of
+        false ->
+            pp_binary_bin_ind(Rest, Orig, Ind, S, I+2, N1, N0, NL, Pos, Acc);
+        true ->
+            Part = binary:part(Orig, S, I),
+            pp_binary_bin_ind(Rest, Orig, Ind, S+I, 2, N0-2, N0, NL+1, 0,
+                              <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+    end;
+pp_binary_bin_ind(<<_,_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+    N1 = N-3,
+    case N1 < 0 of
+        false ->
+            pp_binary_bin_ind(Rest, Orig, Ind, S, I+3, N1, N0, NL, Pos, Acc);
+        true ->
+            Part = binary:part(Orig, S, I),
+            pp_binary_bin_ind(Rest, Orig, Ind, S+I, 3, N0-3, N0, NL+1, Pos,
+                              <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+    end;
+pp_binary_bin_ind(<<_,_,_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+    N1 = N-4,
+    case N1 < 0 of
+        false ->
+            pp_binary_bin_ind(Rest, Orig, Ind, S, I+4, N1, N0, NL, Pos, Acc);
+        true ->
+            Part = binary:part(Orig, S, I),
+            pp_binary_bin_ind(Rest, Orig, Ind, S+I, 4, N0-4, N0, NL+1, Pos,
+                              <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+    end;
+pp_binary_bin_ind(Bin, Orig, Ind, S, I, N, _, NL, Pos0, Acc) ->
+    Col = iolist_size(Ind)+1,
+    Pos = if Pos0 =:= 0 -> Col; true -> Pos0 end,
+    case (byte_size(Bin)-2) > N of  %% Don't count bin closing ">>"
+        false when S =:= 0 ->
+            Sz = byte_size(Orig),
+            {<<Acc/binary, Orig/binary>>, NL, Pos+Sz};
+        false ->
+            Sz = byte_size(Orig)-S,
+            Part = binary:part(Orig, S, Sz),
+            {<<Acc/binary, Part/binary>>, NL, Pos+Sz};
+        true ->
+            Part1 = binary:part(Orig, S, I),
+            Sz = byte_size(Orig) - (S+I),
+            Part2 = binary:part(Orig, S+I, Sz),
+            {<<Acc/binary, Part1/binary, $\n, ?IND(Ind), Part2/binary>>,
+             NL+1, Col+Sz}
+    end.
+
 %% write the whole thing on a single line
+
+write(If, {_, utf8}) ->
+    write_bin(If, <<>>);
+write(If, _) ->
+    write(If).
+
 write({{tuple, _IsTagged, L}, _, _, _}) ->
     [${, write_list(L, $,), $}];
 write({{list, L}, _, _, _}) ->
@@ -419,6 +732,62 @@ write_tail({dots, _, _, _}, S) ->
 write_tail(E, S) ->
     [S | write(E)].
 
+
+%% write the whole thing on a single line
+write_bin({{tuple, _IsTagged, L}, _, _, _}, Acc) ->
+    write_list_bin(L, $,, $}, <<Acc/binary, ${>>);
+write_bin({{list, L}, _, _, _}, Acc) ->
+    write_list_bin(L, $|, $], <<Acc/binary, $[>>);
+write_bin({{map, Pairs}, _, _, _}, Acc) ->
+    write_list_bin(Pairs, $,, $}, <<Acc/binary, "#{">>);
+write_bin({{map_pair, K, V}, _, _, _}, Acc0) ->
+    Acc = write_bin(K, Acc0),
+    write_bin(V, <<Acc/binary, " => ">>);
+write_bin({{record, [{Name0,_} | L]}, _, _, _}, Acc) ->
+    Name = unicode:characters_to_binary(Name0),
+    write_fields_bin(L, <<Acc/binary, Name/binary, ${>>);
+write_bin({{bin, S}, _, _, _}, Acc) ->
+    <<Acc/binary, S/binary>>;
+write_bin({S, _, _, _}, Acc) when is_binary(S) ->
+    <<Acc/binary, S/binary>>;
+write_bin({S, _, _, _}, Acc) ->
+    Bin = unicode:characters_to_binary(S),
+    <<Acc/binary, Bin/binary>>.
+
+write_fields_bin([], Acc) ->
+    <<Acc/binary, $}>>;
+write_fields_bin({dots, _, _, _}, Acc) ->
+    <<Acc/binary, "...}">>;
+write_fields_bin([F | Fs], Acc) ->
+    write_fields_tail_bin(Fs, write_field_bin(F, Acc)).
+
+write_fields_tail_bin([], Acc) ->
+    <<Acc/binary, $}>>;
+write_fields_tail_bin({dots, _, _, _}, Acc) ->
+    <<Acc/binary, ",...}">>;
+write_fields_tail_bin([F | Fs], Acc) ->
+    write_fields_tail_bin(Fs, write_field_bin(F, <<Acc/binary, $,>>)).
+
+write_field_bin({{field, Name, _NameL, F}, _, _, _}, Acc) ->
+    Bin = unicode:characters_to_binary(Name),
+    true = is_binary(Bin),
+    write_bin(F, <<Acc/binary, Bin/binary, " = ">>).
+
+write_list_bin({dots, _, _, _}, _S, End, Acc) ->
+    <<Acc/binary, "...", End>>;
+write_list_bin([E | Es], S, End, Acc) ->
+    write_tail_bin(Es, S, End, write_bin(E, Acc)).
+
+write_tail_bin([], _S, End, Acc) ->
+    <<Acc/binary, End>>;
+write_tail_bin([E | Es], S, End, Acc) ->
+    write_tail_bin(Es, S, End, write_bin(E, <<Acc/binary, $,>>));
+write_tail_bin({dots, _, _, _}, S, End, Acc) ->
+    <<Acc/binary, S, "...", End>>;
+write_tail_bin(E, S, End, Acc) ->
+    Bin = write_bin(E, <<Acc/binary, S>>),
+    <<Bin/binary, End>>.
+
 -type more() :: fun((chars_limit(), DeltaDepth :: non_neg_integer()) ->
                             intermediate_format()).
 
@@ -445,7 +814,9 @@ write_tail(E, S) ->
         }.
 
 -spec intermediate(term(), depth(), pos_integer(), rec_print_fun(),
-                   encoding(), boolean(), boolean()) -> intermediate_format().
+                   encoding() | {encoding, utf8},
+                   boolean(), maps:iterator_order() | undefined) ->
+          intermediate_format().
 
 intermediate(Term, D, T, RF, Enc, Str, Ord) when T > 0 ->
     D0 = 1,
@@ -460,8 +831,8 @@ intermediate(Term, D, T, RF, Enc, Str, Ord) when T > 0 ->
 find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str, Ord, LastLen) ->
     Dd2 = Dd * 2,
     D1 = case D < 0 of
-             true -> Dl + Dd2;
-             false -> min(Dl + Dd2, D)
+             true -> Dl+Dd2;
+             false -> min(Dl+Dd2, D)
          end,
     If = expand(Lower, T, D1 - Dl),
     case If of
@@ -488,7 +859,7 @@ search_depth(Lower, Upper, _Term, T, Dl, Du, _RF, _Enc, _Str, _Ord)
             Upper
     end;
 search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str, Ord) ->
-    D1 = (Dl  + Du) div 2,
+    D1 = (Dl +Du) div 2,
     If = expand(Lower, T, D1 - Dl),
     case If of
 	{_, Len, _, _} when Len > T ->
@@ -521,14 +892,14 @@ print_length(List, D, T, RF, Enc, Str, Ord) when is_list(List) ->
     case Str andalso printable_list(List, D, T, Enc) of
         true ->
             %% print as string, escaping double-quotes in the list
-            S = write_string(List, Enc),
-            {S, io_lib:chars_length(S), 0, no_more};
+            {S, Len} = write_string(List, Enc),
+            {S, Len, 0, no_more};
         {true, Prefix} ->
             %% Truncated lists when T < 0 could break some existing code.
-            S = write_string(Prefix, Enc),
+            {S, Len} = write_string(Prefix, Enc),
             %% NumOfDots = 0 to avoid looping--increasing the depth
             %% does not make Prefix longer.
-            {[S | "..."], 3 + io_lib:chars_length(S), 0, no_more};
+            {[S | "..."], 3 + Len, 0, no_more};
         false ->
             case print_length_list(List, D, T, RF, Enc, Str, Ord) of
                 {What, Len, Dots, _More} when Dots > 0 ->
@@ -561,41 +932,7 @@ print_length(<<_/bitstring>> = Bin, 1, _T, RF, Enc, Str, Ord) ->
     More = fun(T1, Dd) -> ?FUNCTION_NAME(Bin, 1+Dd, T1, RF, Enc, Str, Ord) end,
     {"<<...>>", 7, 3, More};
 print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str, Ord) ->
-    D1 = D - 1,
-    case
-        Str andalso
-        (bit_size(Bin) rem 8) =:= 0 andalso
-        printable_bin0(Bin, D1, tsub(T, 6), Enc)
-    of
-        {true, List} when is_list(List) ->
-            S = io_lib:write_string(List, $"), %"
-            {[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
-        {false, List} when is_list(List) ->
-            S = io_lib:write_string(List, $"), %"
-            {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
-        {true, true, Prefix} ->
-            S = io_lib:write_string(Prefix, $"), %"
-            More = fun(T1, Dd) ->
-                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
-                   end,
-            {[$<,$<,S|"...>>"], 7 + length(S), 3, More};
-        {false, true, Prefix} ->
-            S = io_lib:write_string(Prefix, $"), %"
-            More = fun(T1, Dd) ->
-                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
-                   end,
-            {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
-        false ->
-            case io_lib:write_binary(Bin, D, T) of
-                {S, <<>>} ->
-                    {{bin, S}, iolist_size(S), 0, no_more};
-                {S, _Rest} ->
-                    More = fun(T1, Dd) ->
-                                   ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
-                           end,
-                    {{bin, S}, iolist_size(S), 3, More}
-            end
-    end;    
+    print_length_binary(Bin, D, T, RF, Enc, Str, Ord);
 print_length(Term, _D, _T, _RF, _Enc, _Str, _Ord) ->
     S = io_lib:write(Term),
     %% S can contain unicode, so iolist_size(S) cannot be used here
@@ -735,12 +1072,87 @@ list_length_tail([{_, Len, Dots, _} | Es], Acc, DotsAcc) ->
 list_length_tail({_, Len, Dots, _}, Acc, DotsAcc) ->
     {Acc + 1 + Len, DotsAcc + Dots}.
 
+print_length_binary(Bin, D, T, RF, {InEnc, utf8} = Enc, Str, Ord) ->
+    D1 = D - 1,
+    case
+        Str andalso
+        (bit_size(Bin) rem 8) =:= 0 andalso
+        printable_bin0(Bin, D1, tsub(T, 6), InEnc, binary)
+    of
+        {true, _Bin} ->
+            {Utf8, Len} = io_lib:write_string_bin(Bin, [], latin1),
+            {[$<,$<,$",Utf8,$",$>,$>], 6 + Len, 0, no_more};
+        {false, _Bin} ->
+            {Utf8, Len} = io_lib:write_string_bin(Bin, [], unicode),
+            {[$<,$<,$",Utf8,"\"/utf8>>"], 11 + Len, 0, no_more};
+        {true, true, Prefix} ->
+            {S, Len} = io_lib:write_string_bin(Prefix, [], latin1), %"
+            More = fun(T1, Dd) ->
+                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                   end,
+            {[$<,$<,$",S|"\"...>>"], 9 + Len, 3, More};
+        {false, true, Prefix} ->
+            {S, Len} = io_lib:write_string_bin(Prefix, [], unicode), %"
+            More = fun(T1, Dd) ->
+                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                   end,
+            {[$<,$<,$",S|"\"/utf8...>>"], 14 + Len, 3, More};
+        false ->
+            case io_lib:write_binary_bin(Bin, D, T, <<>>) of
+                {S, <<>>} ->
+                    {{bin, S}, iolist_size(S), 0, no_more};
+                {S, _Rest} ->
+                    More = fun(T1, Dd) ->
+                                   ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                           end,
+                    {{bin, S}, iolist_size(S), 3, More}
+            end
+    end;
+print_length_binary(Bin, D, T, RF, Enc, Str, Ord) ->  %% list output
+    D1 = D - 1,
+    case
+        Str andalso
+        (bit_size(Bin) rem 8) =:= 0 andalso
+        printable_bin0(Bin, D1, tsub(T, 6), Enc, list)
+    of
+        {true, List} when is_list(List) ->
+            S = io_lib:write_string(List, $"), %"
+            {[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
+        {false, List} when is_list(List) ->
+            S = io_lib:write_string(List, $"), %"
+            {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
+        {true, true, Prefix} ->
+            S = io_lib:write_string(Prefix, $"), %"
+            More = fun(T1, Dd) ->
+                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                   end,
+            {[$<,$<,S|"...>>"], 7 + length(S), 3, More};
+        {false, true, Prefix} ->
+            S = io_lib:write_string(Prefix, $"), %"
+            More = fun(T1, Dd) ->
+                           ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                   end,
+            {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
+        false ->
+            case io_lib:write_binary(Bin, D, T) of
+                {S, <<>>} ->
+                    {{bin, S}, iolist_size(S), 0, no_more};
+                {S, _Rest} ->
+                    More = fun(T1, Dd) ->
+                                   ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+                           end,
+                    {{bin, S}, iolist_size(S), 3, More}
+            end
+    end.
+
 %% ?CHARS printable characters has depth 1.
 -define(CHARS, 4).
 
 %% only flat lists are "printable"
 printable_list(_L, 1, _T, _Enc) ->
     false;
+printable_list(L, D, T, {latin1, _}) ->
+    printable_list(L, D, T, latin1);
 printable_list(L, _D, T, latin1) when T < 0 ->
     io_lib:printable_latin1_list(L);
 printable_list(L, _D, T, latin1) when T >= 0 ->
@@ -785,7 +1197,7 @@ is_flat([C|Cs], N) when is_integer(C) ->
 is_flat(_, _N) ->
     false.
 
-printable_bin0(Bin, D, T, Enc) ->
+printable_bin0(Bin, D, T, InEnc, Type) ->
     Len = case D >= 0 of
               true ->
                   %% Use byte_size() also if Enc =/= latin1.
@@ -801,57 +1213,53 @@ printable_bin0(Bin, D, T, Enc) ->
               false when T >= 0 -> % cannot happen
                   T
           end,
-    printable_bin(Bin, Len, D, Enc).
+    printable_bin(Bin, Len, D, InEnc, Type).
 
-printable_bin(_Bin, 0, _D, _Enc) ->
+printable_bin(_Bin, 0, _D, _In, _Out) ->
     false;
-printable_bin(Bin, Len, D, latin1) ->
-    N = erlang:min(20, Len),
-    L = binary_to_list(Bin, 1, N),
-    case printable_latin1_list(L, N) of
-        all when N =:= byte_size(Bin)  ->
-            {true, L};
-        all when N =:= Len -> % N < byte_size(Bin)
-            {true, true, L};
+printable_bin(Bin, Len, D, latin1, Out) when is_binary(Bin) ->
+    case printable_latin1_bin(Bin, Len) of
+        all when Len =:= byte_size(Bin) ->
+            case Out of
+                binary -> {true, Bin};
+                list -> {true, binary_to_list(Bin)}
+            end;
         all ->
-            case printable_bin1(Bin, 1 + N, Len - N) of
-                0 when byte_size(Bin) =:= Len ->
-                    {true, binary_to_list(Bin)};
-                NC when D > 0, Len - NC >= D ->
-                    {true, true, binary_to_list(Bin, 1, Len - NC)};
-                NC when is_integer(NC) ->
-                    false
+            case Out of
+                binary -> {true, true, binary:part(Bin, 0, Len)};
+                list -> {true, true, binary_to_list(Bin, 1, Len)}
+            end;
+        NC when is_integer(NC), D > 0, Len - NC >= D ->
+            case Out of
+                binary -> {true, true, binary:part(Bin, 0, Len - NC)};
+                list -> {true, true, binary_to_list(Bin, 1, Len - NC)}
             end;
-        NC when is_integer(NC), D > 0, N - NC >= D ->
-            {true, true, binary_to_list(Bin, 1, N - NC)};
         NC when is_integer(NC) ->
             false
     end;
-printable_bin(Bin, Len, D, _Uni) ->
-    case valid_utf8(Bin,Len) of
-	true ->
-	    case printable_unicode(Bin, Len, [], io:printable_range()) of
-		{_, <<>>, L} ->
-		    {byte_size(Bin) =:= length(L), L};
-		{NC, Bin1, L} when D > 0, Len - NC >= D ->
-		    {byte_size(Bin)-byte_size(Bin1) =:= length(L), true, L};
-		{_NC, _Bin, _L} ->
-		    false
-	    end;
-	false ->
-	    printable_bin(Bin, Len, D, latin1)
-    end.
-
-printable_bin1(_Bin, _Start, 0) ->
-    0;
-printable_bin1(Bin, Start, Len) ->
-    N = erlang:min(10000, Len),
-    L = binary_to_list(Bin, Start, Start + N - 1),
-    case printable_latin1_list(L, N) of
-        all ->
-            printable_bin1(Bin, Start + N, Len - N);
-        NC when is_integer(NC) ->
-            Len - (N - NC)
+printable_bin(Bin, Len, D, _Uni, Out) ->
+    case printable_unicode_bin(Bin, Len, io:printable_range()) of
+        not_utf8 ->
+            printable_bin(Bin, Len, D, latin1, Out);
+        {N, <<>>} when (Len - N) =:= byte_size(Bin) ->  %% Ascii only
+            case Out of
+                binary ->  {true, Bin};
+                list -> {true, binary_to_list(Bin)}
+            end;
+        {_N, <<>>} ->
+            case Out of
+                binary -> {false, Bin};
+                list -> {false, unicode:characters_to_list(Bin)}
+            end;
+        {N, RestBin} when D > 0, Len - N >= D ->
+            Sz = byte_size(Bin)-byte_size(RestBin),
+            Part = binary:part(Bin, 0, Sz),
+            case Out of
+                binary -> {Sz =:= (Len - N), true, Part};
+                list -> {Sz =:= (Len - N), true, unicode:characters_to_list(Part)}
+            end;
+        _ ->
+            false
     end.
 
 %% -> all | integer() >=0. Adopted from io_lib.erl.
@@ -870,24 +1278,25 @@ printable_latin1_list([$\e | Cs], N) -> printable_latin1_list(Cs, N - 1);
 printable_latin1_list([], _) -> all;
 printable_latin1_list(_, N) -> N.
 
-valid_utf8(<<>>,_) ->
-    true;
-valid_utf8(_,0) ->
-    true;
-valid_utf8(<<_/utf8, R/binary>>,N) ->
-    valid_utf8(R,N-1);
-valid_utf8(_,_) ->
-    false.
+printable_latin1_bin(<<>>, _) -> all;
+printable_latin1_bin(_, 0) -> 0;
+printable_latin1_bin(<<Char:8, Rest/binary>>, N) ->
+    case printable_char(Char, latin1) of
+        true -> printable_latin1_bin(Rest, N-1);
+        false -> N
+    end.
 
-printable_unicode(<<C/utf8, R/binary>>=Bin, I, L, Range) when I > 0 ->
-    case printable_char(C,Range) of
-        true ->
-            printable_unicode(R, I - 1, [C | L],Range);
-        false ->
-            {I, Bin, lists:reverse(L)}
+printable_unicode_bin(<<C/utf8, R/binary>>=Bin, I, Range) when I > 0 ->
+    case printable_char(C, Range) of
+        true -> printable_unicode_bin(R, I-1, Range);
+        false -> {I, Bin}
     end;
-printable_unicode(Bin, I, L,_) ->
-    {I, Bin, lists:reverse(L)}.
+printable_unicode_bin(<<_/utf8, _/binary>>=Bin, I, _Range) ->
+    {I, Bin};
+printable_unicode_bin(<<>>, I, _Range) ->
+    {I, <<>>};
+printable_unicode_bin(_, _, _) ->
+    not_utf8.
 
 printable_char($\n,_) -> true;
 printable_char($\r,_) -> true;
@@ -907,13 +1316,19 @@ printable_char(C,unicode) ->
 
 write_atom(A, latin1) ->
     io_lib:write_atom_as_latin1(A);
+write_atom(A, {latin1, _}) ->
+    io_lib:write_atom_as_latin1(A);
 write_atom(A, _Uni) ->
     io_lib:write_atom(A).
 
-write_string(S, latin1) ->
-    io_lib:write_latin1_string(S, $"); %"
-write_string(S, _Uni) ->
-    io_lib:write_string(S, $"). %"
+write_string(S0, latin1) ->
+    S = io_lib:write_latin1_string(S0, $"), %"
+    {S, io_lib:chars_length(S)};
+write_string(S0, {InEnc, _Out}) ->
+    io_lib:write_string_bin(S0, $", InEnc); %"
+write_string(S0, _Uni) ->
+    S = io_lib:write_string(S0, $"), %"
+    {S, io_lib:chars_length(S)}.
 
 expand({_, _, _Dots=0, no_more} = If, _T, _Dd) -> If;
 expand({{tuple,IsTagged,L}, _Len, _, no_more}, T, Dd) ->
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index ed2edc5cf8..757aa8640e 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -36,7 +36,7 @@
          github_4801/1, chars_limit/1, error_info/1, otp_17525/1,
          unscan_format_without_maps_order/1, build_text_without_maps_order/1]).
 
--export([pretty/2, trf/3]).
+-export([pretty/2, trf/3, rfd/2]).
 
 %%-define(debug, true).
 
@@ -94,9 +94,9 @@ error_1(Config) when is_list(Config) ->
 
 format_neg_zero(Config) when is_list(Config) ->
     <<NegZero/float>> = <<16#8000000000000000:64>>,
-    "-0.000000" = io_lib:format("~f", [NegZero]),
-    "-0.00000e+0" = io_lib:format("~g", [NegZero]),
-    "-0.00000e+0" = io_lib:format("~e", [NegZero]),
+    "-0.000000"   = fmt("~f", [NegZero]),
+    "-0.00000e+0" = fmt("~g", [NegZero]),
+    "-0.00000e+0" = fmt("~e", [NegZero]),
     "-0.0" = io_lib_format:fwrite_g(NegZero),
     ok.
 
@@ -780,7 +780,45 @@ rp(Term, Col, Ll, D, M, RF) ->
     %%           [Col, Ll, D, Term]),
     R = io_lib_pretty:print(Term, Col, Ll, D, M, RF),
     %% io:format("~s~n<--~n", [R]),
-    lists:flatten(io_lib:format("~s", [R])).
+    OrigRes = lists:flatten(io_lib:format("~s", [R])),
+    Args = [{column, Col}, {line_length, Ll}, {depth, D},
+            {line_max_chars, M}, {record_print_fun, RF},
+            %% Default values for print/[1,3,4,5,6]
+            {chars_limit, -1}, {encoding, latin1},
+            {strings, true}, {maps_order, undefined}],
+    check_bin_p(OrigRes, Term, Args).
+
+check_bin_p(OrigRes, Term, Args) ->
+    try
+        {UTF8, _, Width} = io_lib_pretty:print_bin(Term, maps:from_list(Args)),
+
+        StartCol = proplists:get_value(column, Args, 0),
+        OrigW = io_lib_format:indentation(OrigRes, StartCol),
+        case Width > 0 of
+            true when Width == OrigW -> ok;
+            false when (StartCol - Width) == OrigW -> ok;
+            _ ->
+                io:format("Width fail, start: ~w got ~w correct ~w~n", [StartCol, Width, OrigW]),
+                io:put_chars(OrigRes),
+                io:nl(),
+                io:put_chars(UTF8),
+                exit(bad_width)
+        end,
+        true = is_binary(UTF8),
+        case unicode:characters_to_list(UTF8) of
+            OrigRes ->
+                OrigRes;
+            Other ->
+                io:format("Exp: ~w~nGot: ~w~n", [OrigRes, Other]),
+                io:format("Binary failed:~nio_lib_pretty:print_bin(~0p,~n   ~w). ", [Term, Args]),
+                UTF8
+        end
+    catch _:Reason:ST ->
+            io:format("Exp: ~w~n~n", [OrigRes]),
+            io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+            io:format("Binary crashed: io_lib_pretty:print_bin(~p, ~w). ", [Term, Args]),
+            Reason
+    end.
 
 fmt(Fmt, Args) ->
     FormatList = io_lib:scan_format(Fmt, Args),
@@ -790,7 +828,30 @@ fmt(Fmt, Args) ->
     Chars3 = lists:flatten(io_lib:format(Fmt, Args)),
     Chars1 = Chars2,
     Chars2 = Chars3,
-    Chars3.
+    check_bin_fmt(Chars1, Fmt, Args, []).
+
+fmt(Fmt, Args, Opts) ->
+    OrigRes = lists:flatten(io_lib:format(Fmt, Args, Opts)),
+    check_bin_fmt(OrigRes, Fmt, Args, Opts).
+
+check_bin_fmt(OrigRes, Fmt, Args, Opts) ->
+    try
+        Utf8 = io_lib:bformat(Fmt, Args, Opts),
+        true = is_binary(Utf8),
+        case unicode:characters_to_list(Utf8) of
+            OrigRes ->
+                OrigRes;
+            Other ->
+                io:format("Exp: ~w~n     ~ts~nGot: ~w~n     ~ts~n", [OrigRes, OrigRes, Other, Other]),
+                io:format("Binary failed:~nio_lib:bformat(\"~ts\", ~w, ~w). ", [Fmt, Args, Opts]),
+                Utf8
+        end
+    catch _:Reason:ST ->
+            io:format("Exp: ~w~n~n", [OrigRes]),
+            io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+            io:format("Binary crashed: io_lib:bformat(\"~ts\", ~w, ~w). ", [Fmt, Args, Opts]),
+            Reason
+    end.
 
 rfd(a, 0) ->
     [];
@@ -1270,7 +1331,7 @@ ft(V, _F, 0) when is_float(V) ->
 
 g_t(V) when is_float(V) ->
     %% io:format("Testing ~.17g~n", [V]),
-    Io = io_lib:format("~p", [V]),
+    Io = fmt("~p", [V]),
     Sv = binary_to_list(iolist_to_binary(Io)),
     ok = g_t(V, Sv),
     Sv.
@@ -2017,7 +2078,7 @@ printable_range(Suite) when is_list(Suite) ->
     $> = print_max(DNode,
 		   [<<16#10FFFF/utf8,"\t\v\b\f\e\r\n">>,
 		    PrettyOptions]),
-    
+
     1025 = format_max(UNode, ["~tp", [{hello, [1024,1025]}]]),
     125 = format_max(LNode,  ["~tp", [{hello, [1024,1025]}]]),
     125 = format_max(DNode,  ["~tp", [{hello, [1024,1025]}]]),
@@ -2040,8 +2101,47 @@ print_max(Node, Args) ->
 format_max(Node, Args) ->
     rpc_call_max(Node, io_lib, format, Args).
 
-rpc_call_max(Node, M, F, Args) ->
-    lists:max(lists:flatten(rpc:call(Node, M, F, Args))).
+rpc_call_max(Node, io_lib_pretty=M, print=F, [A1,A2]=Args) ->
+    BinAs = [A1, maps:from_list(A2)],
+    Orig = lists:flatten(rpc:call(Node, M, print, Args)),
+    try
+        {Utf8, _, _} = rpc:call(Node, M, print_bin, BinAs),
+        true = is_binary(Utf8),
+        case unicode:characters_to_list(Utf8) of
+            Orig ->
+                lists:max(Orig);
+            Other ->
+                io:format("Exp: ~w~nGot: ~w~n", [Orig, Other]),
+                io:format("Binary failed:~n~w:~w(~0p). ", [M, print_bin, BinAs]),
+                Utf8
+        end
+    catch _:Reason:ST ->
+            io:format("Exp: ~w~n~n", [Orig]),
+            io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+            io:format("Binary crashed: ~w:~w(~0p). ", [M, print_bin, BinAs]),
+            Reason
+    end;
+rpc_call_max(Node, M, format=F, Args) ->
+    {BinF,BinAs} = {bformat, Args},
+    Orig = lists:flatten(rpc:call(Node, M, F, Args)),
+    try
+        Utf8 = rpc:call(Node, M, BinF, BinAs),
+        true = is_binary(Utf8),
+        case unicode:characters_to_list(Utf8) of
+            Orig ->
+                lists:max(Orig);
+            Other ->
+                io:format("Exp: ~w~nGot: ~w~n", [Orig, Other]),
+                io:format("Binary failed:~n~w:~w(~0p). ", [M, BinF, BinAs]),
+                Utf8
+        end
+    catch _:Reason:ST ->
+            io:format("Exp: ~w~n~n", [Orig]),
+            io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+            io:format("Binary crashed: ~w:~w(~0p). ", [M, BinF, BinAs]),
+            Reason
+    end.
+
 
 %% Make sure that a bad specification for a printable range is rejected.
 bad_printable_range(Config) when is_list(Config) ->
@@ -2126,14 +2226,16 @@ pretty(Term, Depth) when is_integer(Depth) ->
     pretty(Term, Opts);
 pretty(Term, Opts) when is_list(Opts) ->
     R = io_lib_pretty:print(Term, Opts),
-    lists:flatten(io_lib:format("~ts", [R])).
+    RF = lists:flatten(R),
+    RF = check_bin_p(RF, Term, Opts),
+    fmt("~ts", [R]).
 
 is_latin1(S) ->
     S >= 0 andalso S =< 255.
 
 %% OTP-10836. ~ts extended to latin1.
 otp_10836(Suite) when is_list(Suite) ->
-    S = io_lib:format("~ts", [[<<"äpple"/utf8>>, <<"äpple">>]]),
+    S = fmt("~ts", [[<<"äpple"/utf8>>, <<"äpple">>]]),
     "äppleäpple" = lists:flatten(S),
     ok.
 
@@ -2208,8 +2310,8 @@ compile_file(File, Text, Config) ->
     end.
 
 io_lib_width_too_small(_Config) ->
-    "**" = lists:flatten(io_lib:format("~2.3w", [3.14])),
-    "**" = lists:flatten(io_lib:format("~2.5w", [3.14])),
+    "**" = fmt("~2.3w", [3.14]),
+    "**" = fmt("~2.5w", [3.14]),
     ok.
 
 %% Test that the time for a huge message queue is not
@@ -2423,11 +2525,12 @@ otp_14178_unicode_atoms(_Config) ->
 
 bad_io_lib_format(F, S) ->
     try io_lib:format(F, S) of
-        _ ->
-            ct:fail({should_fail,F,S})
-    catch
-        error:badarg ->
-            ok
+        _ -> ct:fail({should_fail,F,S})
+    catch error:badarg -> ok
+    end,
+    try io_lib:bformat(F, S) of
+        _ -> ct:fail({should_fail,F,S})
+    catch error:badarg -> ok
     end.
 
 otp_14175(_Config) ->
@@ -2741,7 +2844,7 @@ limt(Term, Depth) when is_integer(Depth) ->
     {{S, S1, S2}, R}.
 
 form(Term, Depth) ->
-    lists:flatten(io_lib:format("~W", [Term, Depth])).
+    lists:flatten(fmt("~W", [Term, Depth])).
 
 limt_pp(Term, Depth) when is_integer(Depth) ->
     T1 = io_lib:limit_term(Term, Depth),
@@ -2750,7 +2853,7 @@ limt_pp(Term, Depth) when is_integer(Depth) ->
     S1 =:= S.
 
 pp(Term, Depth) ->
-    lists:flatten(io_lib:format("~P", [Term, Depth])).
+    lists:flatten(fmt("~P", [Term, Depth])).
 
 otp_14983(_Config) ->
     trunc_depth(-1, fun trp/3),
@@ -2865,44 +2968,45 @@ trp(Term, D, T) ->
     trp(Term, D, T, [{record_print_fun, fun rfd/2}]).
 
 trp(Term, D, T, Opts) ->
-    R = io_lib_pretty:print(Term, [{depth, D},
-                                   {chars_limit, T}|Opts]),
+    R = io_lib_pretty:print(Term, [{depth, D}, {chars_limit, T}|Opts]),
+    FR = lists:flatten(R),
+    FR = check_bin_p(FR, Term, [{depth, D}, {chars_limit, T}|Opts]),
     lists:flatten(io_lib:format("~s", [R])).
 
 trw(Term, D, T) ->
-    lists:flatten(io_lib:format("~W", [Term, D], [{chars_limit, T}])).
+    lists:flatten(fmt("~W", [Term, D], [{chars_limit, T}])).
 
 trf(Format, Args, T) ->
     trf(Format, Args, T, [{record_print_fun, fun rfd/2}]).
 
 trf(Format, Args, T, Opts) ->
-    lists:flatten(io_lib:format(Format, Args, [{chars_limit, T}|Opts])).
+    lists:flatten(fmt(Format, Args, [{chars_limit, T}|Opts])).
 
 otp_15103(_Config) ->
     T = lists:duplicate(5, {a,b,c}),
 
-    S1 = io_lib:format("~0p", [T]),
+    S1 = fmt("~0p", [T]),
     "[{a,b,c},{a,b,c},{a,b,c},{a,b,c},{a,b,c}]" = lists:flatten(S1),
-    S2 = io_lib:format("~-0p", [T]),
+    S2 = fmt("~-0p", [T]),
     "[{a,b,c},{a,b,c},{a,b,c},{a,b,c},{a,b,c}]" = lists:flatten(S2),
-    S3 = io_lib:format("~1p", [T]),
+    S3 = fmt("~1p", [T]),
     "[{a,\n  b,\n  c},\n {a,\n  b,\n  c},\n {a,\n  b,\n  c},\n {a,\n  b,\n"
     "  c},\n {a,\n  b,\n  c}]" = lists:flatten(S3),
 
-    S4 = io_lib:format("~0P", [T, 5]),
+    S4 = fmt("~0P", [T, 5]),
     "[{a,b,c},{a,b,...},{a,...},{...}|...]" = lists:flatten(S4),
-    S5 = io_lib:format("~1P", [T, 5]),
+    S5 = fmt("~1P", [T, 5]),
     "[{a,\n  b,\n  c},\n {a,\n  b,...},\n {a,...},\n {...}|...]" =
         lists:flatten(S5),
     ok.
 
 otp_15159(_Config) ->
     "[atom]" =
-        lists:flatten(io_lib:format("~p", [[atom]], [{chars_limit,5}])),
+        lists:flatten(fmt("~p", [[atom]], [{chars_limit,5}])),
     ok.
 
 otp_15076(_Config) ->
-    {'EXIT', {badarg, _}} = (catch io_lib:format("~c", [a])),
+    ok = bad_io_lib_format("~c", [a]),
     L = io_lib:scan_format("~c", [a]),
     {"~c", [a]} = io_lib:unscan_format(L),
     {'EXIT', {badarg, _}} = (catch io_lib:build_text(L)),
@@ -2975,12 +3079,12 @@ otp_15847(_Config) ->
 
 otp_15875(_Config) ->
     %% This test is moot due to the fix in GH-4842.
-    S = io_lib:format("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]),
+    S = fmt("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]),
     "[{0,[<<\"00\">>]}]" = lists:flatten(S).
 
 
 github_4801(_Config) ->
-	  <<"{[81.6]}">> = iolist_to_binary(io_lib:format("~p", [{[81.6]}], [{chars_limit,40}])).
+	  <<"{[81.6]}">> = iolist_to_binary(fmt("~p", [{[81.6]}], [{chars_limit,40}])).
 
 %% GH-4824, GH-4842, OTP-17459.
 chars_limit(_Config) ->
@@ -2999,7 +3103,8 @@ chars_limit(_Config) ->
     Test = fun (F, N, Lim) ->
                    Opts = [{chars_limit, Lim},
                            {record_print_fun, fun rfd/2}],
-                   [_|_] = io_lib_pretty:print(F(N), Opts)
+                   [_|_] = RL = io_lib_pretty:print(F(N), Opts),
+                   check_bin_p(lists:flatten(RL),F(N), Opts)
            end,
     %% Used to loop:
     Test(List, 1000, 1000),
@@ -3178,19 +3283,19 @@ otp_17525(_Config) ->
          {ddddddddd,1111111111},
          {gggggggggggggggggggg,cccc},
          {uuuuuuuuuuuu,11}],
-    S = io_lib:format("aaaaaaaaaaaaaaaaaa ~p bbbbbbbbbbb ~p",
-                      ["cccccccccccccccccccccccccccccccccccccc", L],
-                      [{chars_limit, 155}]),
-    "aaaaaaaaaaaaaaaaaa \"cccccccccccccccccccccccccccccccccccccc\" bbbbbbbbbbb [{xxxxxxxxx,\n"
-    "                                                                          aaaa},\n"
-    "                                                                         {yyyyyyyyyyyy,\n"
-    "                                                                          1},\n"
-    "                                                                         {eeeeeeeeeeeee,\n"
-    "                                                                          bbbb},\n"
-    "                                                                         {ddddddddd,\n"
-    "                                                                          1111111111},\n"
-    "                                                                         {...}|...]" =
-    lists:flatten(S),
+    S = fmt("aaaaaaaaaaaaaaaaaa ~p bbbbbbbbbbb ~p",
+            ["cccccccccccccccccccccccccccccccccccccc", L],
+            [{chars_limit, 155}]),
+    Corr = "aaaaaaaaaaaaaaaaaa \"cccccccccccccccccccccccccccccccccccccc\" bbbbbbbbbbb [{xxxxxxxxx,\n"
+        "                                                                          aaaa},\n"
+        "                                                                         {yyyyyyyyyyyy,\n"
+        "                                                                          1},\n"
+        "                                                                         {eeeeeeeeeeeee,\n"
+        "                                                                          bbbb},\n"
+        "                                                                         {ddddddddd,\n"
+        "                                                                          1111111111},\n"
+        "                                                                         {...}|...]",
+    Corr = lists:flatten(S),
     ok.
 
 unscan_format_without_maps_order(_Config) ->
-- 
2.43.0

openSUSE Build Service is sponsored by