+for (keys %di) {
+ $info{$_}->{string} = chr(9676) . chr($di{$_}) if $info{$_}->{combining};
+ # find control characters (first 32 chars from 0 and 128)
+ next unless ($di{$_} & ~0b1001_1111) == 0 or $di{$_} == 127;
+ # rename to something more descriptive
+ $info{$_}->{name} = $info{$_}->{unicode10}
+ ? '<'.$info{$_}->{unicode10}.'>' # the old name was much more useful
+ : sprintf('<control U+%04X>', $di{$_}); # at least identify by value
+ # show descriptive symbols instead of control chars themselves
+ $info{$_}->{string} = $di{$_} < 32 ? chr($di{$_} + 0x2400) : chr(0xFFFD);
+}
+
+# convert info hashes into arrays of strings to output in display order
+for my $row (values %info) {
+ $row = [ map { $row->{$_} } qw/name category script string/ ];
+ # strip off trailing missing values (especially string may be unknown)
+ defined $row->[-1] ? last : pop @$row for 1 .. @$row;
+}
+