+use POSIX qw( ceil );
+use Shiar_Sheet::FormatChar;
+my $glyphs = Shiar_Sheet::FormatChar->new;
+my @request;
+
+my $charsets = do 'charset-encoding.inc.pl'
+ or Alert('Encoding metadata could not be read', $@ || $!);
+
+sub tabinput {
+ # generate character table(s)
+ my $input = shift or return;
+ my $params = $input =~ s/[+](.*)\z// ? $1 : undef;
+ my $charset = $charsets->{lc $input} || {};
+
+ if (ref $charset ne 'HASH') {
+ $params and Alert("Parameters ignored for $input",
+ "Cannot apply <q>$params</q> to multiple charsets.",
+ );
+ tabinput($_) for ref $charset ? @{$charset} : $charset;
+ return;
+ }
+
+ state $visible = {'' => 1}; # all present tables
+ my %row = (offset => 0, cols => 16);
+
+ if (not defined $params) {
+ my @parents = @{ $charset->{inherit} || [] };
+
+ if (my ($parent, $part) = pairfirst { defined $visible->{$a} } @parents) {
+ $row{parent} = $parent;
+ $params = $part;
+ $params = 80 unless $visible->{$parent}
+ or ($input eq 'MacCroatian' and defined $visible->{MacRomanian});
+ }
+ elsif (defined $visible->{ascii}) {
+ $row{parent} = $parents[0];
+ $params = $parents[1] // 80;
+ $params = 80 if hex $params >= 0x80; # ascii offset at most
+ }
+ elsif (@parents) {
+ $row{parent} = $parents[0];
+ $params = $parents[1] if hex $parents[1] == 0; # apply ascii end
+ }
+ $visible->{$_} //= 0 for $row{parent} || ();
+ }
+
+ for my $param (split /[+]+/, $params // '') {
+ if ($param eq 'realsize') {
+ $row{realsize}++;
+ }
+ elsif ($param =~ m{ \A cols = (\d+) \z }x) {
+ $row{cols} = $1;
+ }
+ elsif ($param =~ m{ \A (?<start> \p{AHex}+) (?: [-] (?<end> \p{AHex}+) )? \z }x) {
+ if (defined $row{endpoint}) {
+ # extend earlier range
+ my $skip = int(($row{endpoint} || $row{startpoint}) / $row{cols});
+ for ($skip + 1 .. (hex($+{start}) / $row{cols}) - 1) {
+ $row{skip}->{ $_ * $row{cols} - $row{startpoint} }++;
+ }
+ }
+ else {
+ $row{startpoint} = hex $+{start};
+ }
+ $row{endpoint} = hex($+{end} || 0);
+ }
+ else {
+ Alert("Unknown option <q>$param</q> for charset $input");
+ }
+ }
+
+ if ($charset->{setup}) {
+ eval { $charset->{setup}->(\%row) }
+ or Alert("Incomplete setup of $input", $@);
+ }
+ $row{endpoint} ||= 0xFF;
+
+ if (defined $row{table} or defined $row{cell}) {
+ $row{set} //= $input;
+ }
+ elsif ($row{set} = Encode::resolve_alias($input)) {
+ $row{offset} = delete $row{startpoint};
+ if ($charset->{varchar}) {
+ # array of possibly multiple characters per code point
+ $row{table} = [
+ map { Encode::decode($row{set}, pack 'C*', $_) } $row{offset} .. $row{endpoint}
+ ];
+ }
+ else {
+ # ~16x faster than decoding in loop;
+ # substr strings is twice as fast as splitting to an array
+ $row{table} = Encode::decode($row{set}, pack 'C*', $row{offset} .. $row{endpoint});
+ }
+
+ $row{endpoint} -= $row{offset};
+ $visible->{ascii}++; # assume common base
+ }
+ else {
+ Alert("Encoding <q>$input</q> unknown");
+ return;
+ }
+
+ if (my $replace = $charset->{replace}) {
+ while (my ($offset, $sub) = each %{$replace}) {
+ $offset -= $row{offset};
+
+ if (ref $row{table} eq 'ARRAY') {
+ $row{table}->[$offset] = $sub
+ if $offset >= 0 and $offset <= $row{endpoint};
+ next;
+ }
+
+ my $length = length $sub;
+
+ if ($offset < 0) {
+ $offset > -$length or next; # at least one character after start
+ # trim leftmost part to start at offset
+ substr($sub, 0, -$offset) = '';
+ $length += $offset;
+ $offset = 0;
+ }
+
+ if ((my $excess = $row{endpoint} - $offset - $length + 1) < 0) {
+ $excess > -$length or next;
+ # trim rightmost part to prevent overflow
+ substr($sub, $excess) = '';
+ $length += $excess;
+ }
+
+ substr($row{table}, $offset, $length) = $sub;
+ }
+ }
+
+ push @request, \%row;
+ $visible->{ $row{set} } = 1 if $row{table};
+}
+tabinput($_) for @tablist;
+