+ my @colheads;
+ while ($digraphs->[0] !~ /^\./) {
+ my $cell = shift @$digraphs or last;
+ push @colheads, sprintf(
+ '<%s%s>%s',
+ $cell =~ s/^-// ? 'td' : 'th',
+ $cell =~ s/:(.*)// ? qq{ title="$1"} : '',
+ $cell eq '_' ? ' ' : $cell
+ );
+ }
+ push @rows, sprintf '<thead><tr>%s<tbody>', join '', @colheads if @colheads;
+
+ my $colspan = 1;
+ for my $cell (@$digraphs) {
+ if ($cell =~ s/^\.//) {
+ # dot indicates start of a new row
+ push @rows, '';
+ if ($cell =~ s/^>//) {
+ # header cell text follows
+ $cell =~ s/_/ /g; # underscores may be used instead of whitespace (for qw//ability)
+ $rows[-1] .= '<th>'.($cell || ' ');
+ }
+ next;
+ }
+ elsif ($cell eq '>') {
+ # merge this cell to the next column
+ $colspan++;
+ next;
+ }
+
+ my ($code, $name);
+
+ # determine display class
+ my @class;
+ if ($cell eq '-') {
+ $cell = '';
+ }
+ elsif ($cell eq '=') {
+ push @class, 'di-invalid';
+ $cell = '';
+ }
+ else {
+ if ($cell =~ s/^-//) {
+ push @class, 'di-rare'; # discouraged
+ }
+
+ $code = join '', map { $di{ord $_} || '' } split //, $cell;
+ $name = $diinfo->{$code}->[1];
+ length $code == 2 or undef $code;
+
+ if (defined $code) {
+ push @class, 'di-d'; # digraph
+ push @class, 'di-prop' # unofficial
+ if $diinfo->{$code}->[2] =~ /\bXz\b/;
+ }
+ elsif (defined $name) {
+ push @class, 'X';
+ }
+
+ if ($cell =~ /[ -~]/) {
+ push @class, 'di-a'; # ascii
+ }
+ else {
+ push @class, 'di-b'; # basic unicode
+ }
+ }
+
+ # add cell html
+ $rows[-1] .= sprintf('<td%s%s%s>%s%s',
+ defined $name ? qq{ title="$name"} : '',
+ @class ? sprintf(' class="%s"', join ' ', @class) : '',
+ $colspan > 1 && qq{ colspan="$colspan"},
+ $cell eq '' ? ' ' : quote($cell),
+ defined $code ? sprintf(' <small class="digraph">%s</small>', quote($code))
+ : length($cell) == 1 && $cell !~ /[a-z]/
+ ? sprintf(' <small class="%s">%04X</small>', 'value', ord $cell)
+ : '',
+ );
+
+ $colspan = 1;
+ }
+
+ return sprintf qq{<table class="glyphs dilabel">\n%s</table>\n},
+ join '', map {"<tr>$_\n"} @rows;