| 1 | #!/usr/bin/perl
|
|---|
| 2 | use warnings;
|
|---|
| 3 | use strict;
|
|---|
| 4 | $| = 1;
|
|---|
| 5 |
|
|---|
| 6 | my ($input, $outimage, $outstats) = @ARGV;
|
|---|
| 7 | die "need an input file" unless $input;
|
|---|
| 8 | die "need an output image file" unless $outimage;
|
|---|
| 9 | die "need an output stats file" unless $outstats;
|
|---|
| 10 |
|
|---|
| 11 | die "output image file must have extension .png" unless $outimage =~ /\.png$/;
|
|---|
| 12 |
|
|---|
| 13 | my $skipped = 0;
|
|---|
| 14 |
|
|---|
| 15 | my %font_map;
|
|---|
| 16 | my $font_width;
|
|---|
| 17 | my $font_height;
|
|---|
| 18 |
|
|---|
| 19 | sub parse_char {
|
|---|
| 20 | my ($char, $line) = @_;
|
|---|
| 21 | my @lines = grep { defined and length } split /\n/, $char;
|
|---|
| 22 |
|
|---|
| 23 | my $header = shift @lines;
|
|---|
| 24 |
|
|---|
| 25 | my $unicode_value;
|
|---|
| 26 |
|
|---|
| 27 | if ( $header =~ /^U\+([a-fA-F0-9]{4})$/ ) {
|
|---|
| 28 | $unicode_value = unpack "n", pack "H*", $1;
|
|---|
| 29 | } elsif ( length $header == 1 ) {
|
|---|
| 30 | $unicode_value = ord $header;
|
|---|
| 31 | } elsif ( $header =~ /^"(.)"$/ ) {
|
|---|
| 32 | $unicode_value = ord $1;
|
|---|
| 33 | } elsif ( $header =~ /^'(.)'$/ ) {
|
|---|
| 34 | $unicode_value = ord $1;
|
|---|
| 35 | } elsif ( $header eq "TODO" or $header eq "SKIP" ) {
|
|---|
| 36 | $skipped++;
|
|---|
| 37 | return;
|
|---|
| 38 | } else {
|
|---|
| 39 | die "can't handle a header of \"$header\" (line $line)";
|
|---|
| 40 | }
|
|---|
| 41 |
|
|---|
| 42 | my $height = @lines;
|
|---|
| 43 | my $width = length $lines[0];
|
|---|
| 44 |
|
|---|
| 45 | length $lines[$_] == $width or die "Non-rectangular character \"$header\" (line $line)"
|
|---|
| 46 | for 0 .. $#lines;
|
|---|
| 47 |
|
|---|
| 48 | $font_width = $width unless defined $font_width;
|
|---|
| 49 | $font_height = $height unless defined $font_height;
|
|---|
| 50 |
|
|---|
| 51 | die "Character dimensions (${width}x${height}) for \"$header\" do not match font dimensions (${font_width}x${font_height}) (line $line)"
|
|---|
| 52 | if $font_width != $width or $font_height != $height;
|
|---|
| 53 |
|
|---|
| 54 | tr/[., ]/0/, tr/[1X@MOSW]/1/ for @lines;
|
|---|
| 55 |
|
|---|
| 56 | ($_ ne '1' and $_ ne '0') and die "Bad character value '$_' for \"$header\" (line $line)"
|
|---|
| 57 | for map split(//, $_), @lines;
|
|---|
| 58 |
|
|---|
| 59 | $font_map{$unicode_value} = \@lines;
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | open my $lf, "<", $ARGV[0] or die;
|
|---|
| 63 | my $char_data = '';
|
|---|
| 64 | my $char_line_no;
|
|---|
| 65 | my $line_no = 0;
|
|---|
| 66 | while ( <$lf> ) {
|
|---|
| 67 | chomp;
|
|---|
| 68 | s/\s*#.*$//;
|
|---|
| 69 | if ( length ) {
|
|---|
| 70 | $char_line_no = $line_no unless defined $char_line_no;
|
|---|
| 71 | $char_data .= "$_\n";
|
|---|
| 72 | } elsif ( length $char_data ) {
|
|---|
| 73 | parse_char($char_data, $char_line_no);
|
|---|
| 74 | $char_data = '';
|
|---|
| 75 | undef $char_line_no;
|
|---|
| 76 | }
|
|---|
| 77 | }
|
|---|
| 78 | parse_char($char_data) if length $char_data;
|
|---|
| 79 | close $lf;
|
|---|
| 80 |
|
|---|
| 81 | print STDERR "Characters: parsed ".scalar(keys %font_map).", skipped $skipped\n";
|
|---|
| 82 | print STDERR "Font size: ${font_width}x${font_height}\n";
|
|---|
| 83 |
|
|---|
| 84 | my @codepoints = sort { $a <=> $b } keys %font_map;
|
|---|
| 85 |
|
|---|
| 86 | my $width = int sqrt int scalar @codepoints;
|
|---|
| 87 | $width = 8 if $width < 8;
|
|---|
| 88 |
|
|---|
| 89 | print STDERR "Building image and stats...\n";
|
|---|
| 90 | open my $sf, ">", $outstats or die;
|
|---|
| 91 |
|
|---|
| 92 | my $this_ch_x = 0;
|
|---|
| 93 | my $this_ch_y = 0;
|
|---|
| 94 | my $image_data = '';
|
|---|
| 95 | my @row_bits = map +("0" x ($width*$font_width)), 1..$font_height;
|
|---|
| 96 | my $last_cp = 0;
|
|---|
| 97 | my $cp_run = 0;
|
|---|
| 98 | sub flush_run {
|
|---|
| 99 | if ( $cp_run ) {
|
|---|
| 100 | print $sf "r$cp_run\n";
|
|---|
| 101 | $cp_run = 0;
|
|---|
| 102 | }
|
|---|
| 103 | }
|
|---|
| 104 | for my $idx ( 0 .. $#codepoints ) {
|
|---|
| 105 | my $cp = $codepoints[$idx];
|
|---|
| 106 |
|
|---|
| 107 | # flush this row to the pbm buffer if neccessary
|
|---|
| 108 | if ( $this_ch_x == $width ) {
|
|---|
| 109 | $this_ch_y++;
|
|---|
| 110 | $this_ch_x = 0;
|
|---|
| 111 | for my $row ( @row_bits ) {
|
|---|
| 112 | $row .= "0" while length($row) % 8 != 0;
|
|---|
| 113 | $image_data .= pack "B*", $row;
|
|---|
| 114 | $row = "0" x ($width*$font_width);
|
|---|
| 115 | }
|
|---|
| 116 | print "image data length: ".length($image_data)."\n";
|
|---|
| 117 | flush_run();
|
|---|
| 118 | print $sf "y\n";
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 |
|
|---|
| 122 | # add the character to this image row
|
|---|
| 123 | if ( $last_cp + 1 == $cp ) {
|
|---|
| 124 | $cp_run++;
|
|---|
| 125 | $last_cp = $cp;
|
|---|
| 126 | } else {
|
|---|
| 127 | flush_run();
|
|---|
| 128 | print $sf "$cp\n";
|
|---|
| 129 | $last_cp = $cp;
|
|---|
| 130 | }
|
|---|
| 131 |
|
|---|
| 132 | for my $row ( 0 .. $font_height-1 ) {
|
|---|
| 133 | substr($row_bits[$row], $this_ch_x*$font_width, $font_width, $font_map{$cp}[$row]);
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | $this_ch_x++;
|
|---|
| 137 | }
|
|---|
| 138 |
|
|---|
| 139 | flush_run();
|
|---|
| 140 |
|
|---|
| 141 | if ( $this_ch_x ) {
|
|---|
| 142 | # we have put a character on the last line
|
|---|
| 143 | for my $row ( @row_bits ) {
|
|---|
| 144 | $row .= "0" while length($row) % 8 != 0;
|
|---|
| 145 | $image_data .= pack "B*", $row;
|
|---|
| 146 | }
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | close $sf;
|
|---|
| 150 |
|
|---|
| 151 | print STDERR "Converting image to png...\n";
|
|---|
| 152 |
|
|---|
| 153 | open $sf, "|-", "convert", "pbm:-", "png:$outimage" or die;
|
|---|
| 154 | print $sf "P4 ".($font_width * $width)." ".(($this_ch_y+1) * $font_height)." ".$image_data;
|
|---|
| 155 | close $sf;
|
|---|
| 156 |
|
|---|
| 157 | print STDERR "Done.\n";
|
|---|
| 158 |
|
|---|