charset: track table offset in loop
[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         my $offset = 0;
166         my $endpoint = $offset + (length($row->{table}) || 256) * $colsize;
167
168         printf '<div class="section"><table class="glyphs%s">', !$row->{cell} && ' charmap';
169         my $title = $row->{set};
170         $title .= " <aside>($_)</aside>" for $row->{setnote} // ();
171         printf '<caption>%s</caption>', $title;
172         print '<col>' x ($cols + 1);
173         for my $section (qw{thead}) {
174                 print "<$section><tr><th>↱";
175                 printf '<th>%0*X', $coldigits, $_ * $colsize for 0 .. $cols - 1;
176                 print "\n";
177         }
178         print '<tbody>';
179         while ($offset < $endpoint - 1) {
180                 print '<tr><th>';
181                 {
182                         if (my $rowmod = $offset % $rowdiv) {
183                                 # offset in column units
184                                 printf '<small>+%X</small>', $rowmod;
185                         }
186                         else {
187                                 # divided row offset
188                                 printf '%X', ($offset + $row->{offset}) / $rowdiv;
189                         }
190                 }
191                 for (1 .. $cols) {
192                         if ($row->{cell}) {
193                                 print range_cell($row, $offset);
194                                 next;
195                         }
196
197                         my $glyph = substr $row->{table}, $offset, 1;
198                         if ($glyph eq $NOCHAR) {
199                                 print '<td>';
200                                 next;
201                         }
202
203                         print "\n".$glyphs->glyph_cell($glyph);
204                 }
205                 continue {
206                         $offset += $colsize;
207                 }
208                 print "\n";
209         }
210         say '</table></div>';
211 }
212
213 :>
214 <hr>
215
216 <div class="legend">
217         <table class="glyphs"><tr>
218         <td class="X Cc">control
219         <td class="X Zs"><span>whitespace</span>
220         <td class="X Mn">diacritic<table class="glyphs"><tr>
221                 <td class="X Sk">letter
222                 </table>
223         <td class="X Po">punctuation<table class="glyphs"><tr>
224                 <td class="X Pf">quote
225                 </table>
226         <td class="X So">symbol<table class="glyphs"><tr>
227                 <td class="X Sm">math
228                 <td class="X Sc">currency
229                 </table>
230         <td class="X No">numeric
231         <td class="X Greek">greek<table class="glyphs"><tr>
232                 <td class="X Latin">latin
233                 <td class="X Cyrillic">cyrillic
234                 </table>
235         <td class="X Aramaic">aramaic<table class="glyphs"><tr>
236                 <td class="X Brahmic">brahmic
237                 <td class="X Arabic">arabic
238                 </table>
239         <td class="X Syllabic">syllabic<table class="glyphs"><tr>
240                 <td class="X African">african
241                 <td class="X Hiragana">japanese
242                 <td class="X Han">cjk
243                 <td class="X Bopomofo">chinese
244                 </table>
245         <td class="X Alpha">alphabetic
246         </table>
247
248         <table class="glyphs"><tr>
249         <td class="X">unicode 7.0
250         <td class="X Xr">proposed
251         <td class="X Xd">deprecated
252         <td class="">unassigned
253         <td class="X Xi">invalid
254         </table>
255 </div>
256