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