charset: distinct column count per table
[sheet.git] / charset.plp
1 <(common.inc.plp)><:
2
3 Html({
4         title => 'charset cheat sheet',
5         version => '1.0',
6         description => [
7                 "Reference sheet with all glyphs in common character encoding tables,",
8                 "and an overview of Unicode ranges and UTF-8 bytes.",
9         ],
10         keywords => [qw'
11                 charset codepage unicode ascii utf8 latin glyph character encoding
12                 reference common overview table
13         '],
14         stylesheet => [qw'light'],
15         data => [qw'charset-unicode.inc.pl charset-utf8.inc.pl'],
16 });
17
18 :>
19 <h1>Character encoding</h1>
20
21 <:
22 use POSIX qw( ceil );
23 use Shiar_Sheet::FormatChar;
24 my $glyphs = Shiar_Sheet::FormatChar->new;
25
26 # generate character table(s)
27 # (~16x faster than decoding in loop;
28 #  substr strings is twice as fast as splitting to an array)
29 my %ALIAS = (
30 #       default => [qw(unicode utf-8 iso-8859-1 cp437 -cp1252- --iso-8859-15- -koi8-f)],
31         default => [qw(unicode- utf-8 iso-8859-1 -cp1252- --iso-8859-15- cp437 -cp850)],
32         0 => [qw(cp437 -cp863)],
33         1 => [qw(iso-8859-1 -cp1252 -MacRoman -cp850)],
34         2 => [qw(iso-8859-2 -cp1250 -cp852 -MacCentralEurRoman -MacCroatian -MacRumanian)],
35         5 => [qw(koi8-f -iso-8859-5 -cp1251 -MacCyrillic -cp855 -cp866)],
36         7 => [qw(iso-8859-7 -cp1253 -MacGreek -cp737 -cp869)],
37         8 => [qw(iso-8859-8 -cp1255 -MacHebrew -cp862)],
38 );
39 my @request = map {
40         if (my $input = $_) {
41                 my %row = (offset => 0, cols => 16);
42                 my $endpoint = 255;
43                 if ($input =~ s/^--//) {
44                         $row{offset} = $endpoint > 160 ? 160 : 48;
45                 }
46                 elsif ($input =~ s/^-//) {
47                         $row{offset} = $endpoint > 128 ? 128 : 32;
48                 }
49                 if ($input =~ s/-$//) {
50                         $endpoint = $row{offset} ? $row{offset} < 160 ? 159 : 191 : 127;
51                 }
52                 if ($row{offset}) {
53                         $row{setnote} = 'over cp437' if $input eq 'cp850';
54                         $row{setnote} = 'over iso-8859-1' if $input =~ /^iso-8859-|^cp125/;
55                 }
56
57                 if ($input =~ /^U([0-9a-f]+)(?:-([0-9a-f]+))?/) {
58                         my $start = hex($1) << ($2 ? 4 : 8);
59                         my $end = $2 ? hex($2) << 4 : $start + 240;
60                         $row{table} = join '', map { chr } $start .. $end+15;
61                         utf8::upgrade($row{table});  # prevent latin1 output
62                         $row{set} = sprintf 'Unicode block U+%02Xxx', $start >> 8;
63                 }
64                 elsif ($input eq 'U') {
65                         $row{table} = ' ' x 1024;
66                         $row{set} = 'Unicode planes';
67                         $row{cell} = do 'charset-ucplanes.inc.pl'
68                                 or Alert('Table data could not be read', $@ || $!);
69                         $row{cols} *= 2;
70                 }
71                 elsif ($row{set} = Encode::resolve_alias($input)) {
72                         if ($row{set} eq 'Internal') {
73                                 $row{table} = ' ' x ($endpoint < 255 ? 640 : 8192);
74                                 $row{set} = 'Unicode BMP';
75                                 $row{cell} = do 'charset-unicode.inc.pl'
76                                         or Alert('Table data could not be read', $@ || $!);
77                         }
78                         elsif ($row{set} eq 'utf-8-strict') {
79                                 $row{table} = undef;
80                                 $row{set} = 'UTF-8';
81                                 $row{cell} = do 'charset-utf8.inc.pl'
82                                         or Alert('Table data could not be read', $@ || $!);
83                         }
84                         else {
85                                 $row{table} = Encode::decode($row{set}, pack 'C*', $row{offset} .. $endpoint);
86                         }
87                 }
88                 else {
89                         Alert("Encoding <q>$input</q> unknown");
90                 }
91                 $row{set} ? \%row : ();
92         }
93         else {
94                 ();
95         }
96 } map { defined $ALIAS{$_} ? @{ $ALIAS{$_} } : $_ }
97         $Request =~ /\w/ ? split(m{[/+\s]}, $Request) : 'default';
98 my $NOCHAR = chr 0xFFFD;
99
100 for my $cp437 (grep {$request[$_]->{set} eq 'cp437'} 0 .. $#request) {
101         substr($request[$cp437]->{table}, 237, 1) = pack 'U*', 0x3D5; # phi sign
102         substr($request[$cp437]->{table}, 0, 32) = pack 'U*', map {hex} qw(
103                 2007 263A 263B 2665 2666 2663 2660 2022 25D8 25CB 25D9 2642 2640 266A 266B 263C
104                 25BA 25C4 2195 203C 00B6 00A7 25AC 21A8 2191 2193 2192 2190 221F 2194 25B2 25BC
105         );
106 }
107
108 sub range_cell {
109         my ($info, $offset) = @_;
110         my $table = $info->{cell} or return;
111         my $def = $table->{$offset} or return;
112         my ($len, $class, $name, $title) = @{$def};
113
114         my $cols = $info->{cols};
115         my $colsize = $table->{colsize} || 1;
116         my $attr = '';
117         $len /= $colsize;
118         $name //= $len <= 2 ? 'res' : 'reserved';
119
120         if (my $part = $offset/$colsize % $cols) {
121                 # continued row
122                 my $rest = $cols - $part;  # remaining
123                 $rest = $len if $len < $rest; #TODO: optimise
124                 if ($len -= $rest) {
125                         # continued on new row
126                         my @next = ($len * $colsize, "$class joinu");
127                         if ($len > $rest) {
128                                 # minority remains
129                                 push @next, $name, $title;
130                                 $title ||= $name;
131                                 $name = '';
132                         }
133                         else {
134                                 # minority on next row
135                                 push @next, '"', $title || $name;
136                         }
137                         $table->{$offset + $colsize*$rest} //= \@next;
138                         $class .= ' joind';
139                 }
140                 $len = $rest;
141         }
142         elsif (my $rows = int($len / $cols)) {
143                 # multiple full rows
144                 if ($len -= $rows * $cols) {
145                         # partial row remains
146                         $table->{$offset + $colsize*$rows * $cols} //= [$len*$colsize, "$class joinu", '', $title];
147                         $class .= ' joind';
148                 }
149                 $attr .= sprintf ' rowspan=%d', $rows;
150                 $len = $cols;
151         }
152
153         $attr .= sprintf ' colspan=%d', $len unless $len == 1;
154         $attr .= $1 if $class and $class =~ s/( \w+="[^"]*")//;
155         $attr .= sprintf ' class="%s"', $class if $class;
156         $attr .= sprintf ' title="%s"', EscapeHTML($title) if $title;
157         return "<td$attr>$name";
158 }
159
160 for my $row (@request) {
161         my $cols = $row->{cols};
162         my $colsize = $row->{cell} && $row->{cell}->{colsize} || 1;
163         my $coldigits = ceil(log($colsize * $cols) / log(16));  # uniform length of hexadecimal header
164         my $rowdiv = 16 ** $coldigits;  # divider of row headers
165
166         printf '<div class="section"><table class="glyphs%s">', !$row->{cell} && ' charmap';
167         my $title = $row->{set};
168         $title .= " <aside>($_)</aside>" for $row->{setnote} // ();
169         printf '<caption>%s</caption>', $title;
170         print '<col>' x ($cols + 1);
171         for my $section (qw{thead}) {
172                 print "<$section><tr><th>↱";
173                 printf '<th>%0*X', $coldigits, $_ * $colsize for 0 .. $cols - 1;
174                 print "\n";
175         }
176         print '<tbody>';
177         for my $msb (0 .. ((length($row->{table}) || 256) - 1) / $cols) {
178                 print '<tr><th>';
179                 {
180                         my $rowlabel = ($msb + int($row->{offset} / $cols)) * $cols * $colsize;
181                         if (my $rowmod = $rowlabel % $rowdiv) {
182                                 # offset in column units
183                                 printf '<small>+%X</small>', $rowmod;
184                         }
185                         else {
186                                 # divided row offset
187                                 printf '%X', $rowlabel / $rowdiv;
188                         }
189                 }
190                 for my $lsb (0 .. $cols - 1) {
191                         my $val = ( ($msb * $cols) + $lsb ) * $colsize;
192                         if ($row->{cell}) {
193                                 print range_cell($row, $val);
194                                 next;
195                         }
196
197                         my $glyph = substr $row->{table}, $val, 1;
198                         if ($glyph eq $NOCHAR) {
199                                 print '<td>';
200                                 next;
201                         }
202
203                         print "\n".$glyphs->glyph_cell($glyph);
204                 }
205                 print "\n";
206         }
207         say '</table></div>';
208 }
209
210 :>
211 <hr>
212
213 <div class="legend">
214         <table class="glyphs"><tr>
215         <td class="X Cc">control
216         <td class="X Zs"><span>whitespace</span>
217         <td class="X Mn">diacritic<table class="glyphs"><tr>
218                 <td class="X Sk">letter
219                 </table>
220         <td class="X Po">punctuation<table class="glyphs"><tr>
221                 <td class="X Pf">quote
222                 </table>
223         <td class="X So">symbol<table class="glyphs"><tr>
224                 <td class="X Sm">math
225                 <td class="X Sc">currency
226                 </table>
227         <td class="X No">numeric
228         <td class="X Greek">greek<table class="glyphs"><tr>
229                 <td class="X Latin">latin
230                 <td class="X Cyrillic">cyrillic
231                 </table>
232         <td class="X Aramaic">aramaic<table class="glyphs"><tr>
233                 <td class="X Brahmic">brahmic
234                 <td class="X Arabic">arabic
235                 </table>
236         <td class="X Syllabic">syllabic<table class="glyphs"><tr>
237                 <td class="X African">african
238                 <td class="X Hiragana">japanese
239                 <td class="X Han">cjk
240                 <td class="X Bopomofo">chinese
241                 </table>
242         <td class="X Alpha">alphabetic
243         </table>
244
245         <table class="glyphs"><tr>
246         <td class="X">unicode 7.0
247         <td class="X Xr">proposed
248         <td class="X Xd">deprecated
249         <td class="">unassigned
250         <td class="X Xi">invalid
251         </table>
252 </div>
253