c6c890011abb33a33a873de67c9edaa4a40c9a59
[sheet.git] / tools / mkcharinfo
1 #!/usr/bin/env perl
2 use 5.010;
3 use strict;
4 use warnings;
5 use utf8;
6 no if $] >= 5.018, warnings => 'experimental::smartmatch';
7
8 use open OUT => ':utf8', ':std';
9 use Data::Dump 'pp';
10
11 our $VERSION = '1.00';
12
13 my %info = (
14         "\xAD"     => {string => '-'},
15         "\x{200E}" => {string => '→'},
16         "\x{200F}" => {string => '←'},
17 );
18 $info{chr $_} //= {} for 32 .. 126;
19
20 eval {
21         my $tables = do 'unicode-table.inc.pl' or die $@ || $!;
22         for (values %$tables) {
23                 for (values %$_) {
24                         for (@$_) {
25                                 length $_ == 1 or next;  # ignore meta values
26                                 s/\\//;  # unescape
27                                 $info{$_} //= {};
28                         }
29                 }
30         }
31         1;
32 } or warn "Failed reading unicode tables: $@";
33
34 eval {
35         require HTML::Entities;
36         our %char2entity;
37         HTML::Entities->import('%char2entity');
38         while (my ($char, $entity) = each %char2entity) {
39                 $entity =~ /[a-zA-Z]/ or next;  # only actual aliases
40                 $info{$char}->{html} = substr($entity, 1, -1);
41         }
42         1;
43 } or warn "Failed importing html entities: $@";
44
45 my %diinc = (
46         'digraphs.inc.pl' => 'u-di',
47 );
48 for (keys %diinc) {
49         -e $_ or next;
50         my $di = do $_ or die "Error reading digraphs file $_: ", $@ || $!;
51         while (my ($mnem, $cp) = each %$di) {
52                 length $mnem == 2 or next;  # limit to digraphs
53                 my $class = $diinc{$_};
54                 if (ref $cp) {
55                         # old style array
56                         $class = 'u-prop' if $cp->[2] and $cp->[2] =~ m/\bXz\b/;
57                         $cp = chr $cp->[0];
58                 }
59                 $info{$cp}->{di} //= $mnem;
60                 $info{$cp}->{class}->{$class}++;
61         }
62 }
63
64 eval {
65         # read introducing unicode versions for known characters
66         my $agemap = do 'unicode-age.inc.pl' or die $@ || $!;
67         for my $chr (keys %info) {
68                 my $version = $agemap->{ord $chr} or next;
69                 $info{$chr}->{class}->{'u-v'.$version}++
70         }
71         1;
72 } or warn "Failed including unicode version data $@";
73
74 for my $chr (keys %info) {
75         my $cp = ord $chr;
76         # attempt to get unicode character information
77         my $info = eval {
78                 require Unicode::UCD;
79                 Unicode::UCD::charinfo($cp)
80                         || { block => '?', category => 'Xn', name => '', script => '' }
81         } or next;
82
83         $info->{$_} = $info{$chr}->{$_} for keys %{ $info{$chr} };
84
85         # categorise by unicode types and writing script
86         $info->{class}->{$_}++ for $info->{category};
87         $info->{class}->{$_}++ for $info->{script} || ();
88
89         # add custom categories for certain blocks
90         $info->{class}->{Xa}++ if $info->{block} eq 'Basic Latin';
91         $info->{class}->{Xl}++ if $info->{block} eq 'Latin-1 Supplement';
92
93         given ($cp) {
94                 when ($info->{string}) {
95                         # keep predefined presentational string
96                 }
97                 when ($info->{combining}) {
98                         # overlay combining accents
99                         $info->{string} = chr(9676) . $chr;
100                 }
101                 when (($cp & ~0b1001_1111) == 0 or $cp == 127) {
102                         # control characters (first 32 chars from 0 and 128)
103                         # rename to something more descriptive
104                         $info->{name} = $info->{unicode10}
105                                 ? '<'.$info->{unicode10}.'>'  # the old name was much more useful
106                                 : sprintf('<control U+%04X>', $cp);  # at least identify by value
107                         # show descriptive symbols instead of control chars themselves
108                         $info->{string} = $cp < 32 ? chr($cp + 0x2400) : chr(0xFFFD);
109                 }
110         }
111
112         $info{$chr} = $info;
113 }
114
115 # output perl code of hash
116 say 'use utf8;';
117 say '+{';
118 for my $cp (sort keys %info) {
119         $info{$cp}->{classstr} = join(' ', sort keys %{ $info{$cp}->{class} });
120         # convert info hashes into arrays of strings to output in display order
121         my $row = [ map { $info{$cp}->{$_} } qw/classstr name di html string/ ];
122         # strip off trailing missing values (especially string may be unknown)
123         defined $row->[-1] ? last : pop @$row for 1 .. @$row;
124         # final line (assume safe within single quotes)
125         say sprintf '"\x{%X}" => [%s],',
126                 ord $cp, join(',', map { escapeq($_) } @$row);
127 }
128 say '}';
129
130 sub escapeq {
131         local $_ = shift;
132         return 'undef' if not defined;
133         s/(['\\])/\\$1/g;
134         return "'$_'";
135 }
136
137 __END__
138
139 =head1 NAME
140
141 mkcharinfo - Gather Unicode character details in Perl array
142
143 =head1 SYNOPSIS
144
145     mkcharinfo > unicode-char.inc.pl
146
147 Test by printing the description of U+0041 (latin A):
148
149     perl -e'$u = do "unicode-char.inc.pl"; print $u->{A}->[1]'
150
151 =head1 AUTHOR
152
153 Mischa POSLAWSKY <perl@shiar.org>
154
155 =head1 LICENSE
156
157 Licensed under the GNU Affero General Public License version 3.
158