#!/usr/bin/perl

#    Converts Glyph Bitmat Distribution Format (BDF) glyph
#    information to C data structures.  Rotates glyphs
#    90 degrees clockwise for POV projects, etc.
#
#    Copyright (C) 2011  Aaron W Morris <aaron@aarmor.net>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    <http://www.gnu.org/licenses/>.
#
#    If you found this program useful, please send me an email.
#    I wasted a lot of time on this hunk of junk. :-)


use strict;
use utf8;
#use Data::Dumper;
$\ = $/;

# Template file name
my $OUTFILE = 'my_fontdata_%dx%d.h';


&main();


sub dec2bin {
    my $str = unpack('B32', pack('N', shift));
    $str =~ s/^0+(?=\d)//; # otherwise you'll get leading zeros
    return $str;
} #end sub dec2bin

sub bin2dec {
    return unpack('N', pack('B32', substr('0' x 32 . shift, -32)));
} #end sub bin2dec

sub getFontData {
    my ($bcdfile,$char_size,$formats) = @_;

    open(BDF,'<:utf8',$bcdfile) or die('Cannot open file: ' . $!);
    my $line1 = <BDF>;
    chomp($line1);

    # Require format 2.1, have not found any 2.2 files to test
    die('Wrong format') if($line1 !~ /^STARTFONT 2\.1$/);

    my $chars = [];
    my $char_num;

    while(<BDF>) {
        my $line = $_;
        chomp($line);

        if($line =~ /^FONTBOUNDINGBOX (.+?) (.+?) (.+?) (.+?)$/) {
            $char_size->{'x'} = $1;
            $char_size->{'y'} = $2;
            $char_size->{'offset'} = $4;
            $formats->{'hex'} = '0x%0' . (&calcDataSize($char_size) / 4) . 'x';
            $formats->{'bin'} = '%0' . &calcDataSize($char_size) . 's';
            next();
        }

        if($line =~ /^ENCODING (.+)$/) {
            $char_num = $1;
            next();
        }

        # Debugging to pull out a particular character (below as well)
        #next() if($char_num != 64);

        next() if($line !~ /^BITMAP/); #unimportant line, ignore

        # The next several lines after "BITMAP" are the bitmap info

        # Get old bit map information in hex
        my @old_bitmap;
        while(1) {
            my $val = <BDF>;
            chomp($val);

            last() if($val =~ /^ENDCHAR/); #marks end of bitmap data

            push(@old_bitmap,sprintf($formats->{'hex'},hex($val)));
        }

        # Convert to visual bitmap, convert hex numbers to binary
        # unnecessary, mainly for debugging
        my @old_bitmap_bin;
        foreach(0 .. $#old_bitmap) {
            $old_bitmap_bin[$_] = sprintf($formats->{'bin'},dec2bin(hex($old_bitmap[$_])));
        }

        # Rotate bitmap
        my @new_bitmap_bin;
        foreach my $bit (0 .. (&calcDataSize($char_size) - 1)) {
            foreach(reverse(0 .. $#old_bitmap)) {
                if(hex($old_bitmap[$_]) & (1 << $bit)) {
                    $new_bitmap_bin[$bit] .= '1';
                } else {
                    $new_bitmap_bin[$bit] .= '0';
                }
            }
        }
        @new_bitmap_bin = reverse(@new_bitmap_bin);


        # new bitmap info in hex, convert binary numbers to hex
        my @new_bitmap;
        foreach(0 .. $#new_bitmap_bin) {
            $new_bitmap[$_] = sprintf($formats->{'hex'},bin2dec($new_bitmap_bin[$_]));
        }

        # trim the fat
        # remove unused lines from the bitmap
        splice(@new_bitmap,0,(&calcDataSize($char_size) - $char_size->{'y'} + ($char_size->{'offset'} * -1)));  # from beginning
        splice(@new_bitmap,((($#new_bitmap+1) - $char_size->{'x'}) * -1));  # from end

        # Debugging
        #printf('%d %s' . "\n",$char_num,join(',',@old_bitmap));
        #printf("old\n%s\n",join("\n",@old_bitmap_bin));
        #printf("new\n%s\n",join("\n",@new_bitmap_bin));
        #printf('%d %s' . "\n",$char_num,join(',',@new_bitmap));

        $chars->[$char_num] = {};
        $chars->[$char_num]->{'char_num'} = $char_num;
        $chars->[$char_num]->{'bitmap'} = [@new_bitmap];

        # Debugging to pull out a particular character (above as well)
        #last();
    } 

    close(BDF);

    return($chars);
} #end sub getFontData

sub writeSourceC {
    my ($chars,$char_size,$formats) = @_;
    my ($hex,$comment);

    # format for output
    format cdata =
         @>>>>>>>>>>,    //  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
         $hex,               $comment
.

    local $~ = 'cdata';

    close(STDOUT);
    my $outfilename = sprintf($OUTFILE,$char_size->{'x'},$char_size->{'y'});
    open(STDOUT,'>:utf8',$outfilename) or die('Cannot open file: ' . $!);
    
    print sprintf('const uint%d_t my_fontdata_%dx%d[%d][%d] = {',
     &calcDataSize($char_size),
     $char_size->{'x'},
     $char_size->{'y'},
     $#{$chars}+1,
     $char_size->{'x'});

    foreach my $char (@{$chars}) {
        #$my $print_char = chr($char->{'char_num'}); #this does not always work
        #$print_char =~ s/\n/newline/;
        #$print_char =~ s/\t/tab/;

        print sprintf('    {                    //  char %d',
         $char->{'char_num'});
         #$print_char,

        # draw the bitmap
        foreach $hex (@{$char->{'bitmap'}}) {
            $comment = sprintf($formats->{'bin'},dec2bin(hex($hex)));
            $comment =~ s/0/\ /g;
            $comment =~ s/1/*/g;
            write;
        }
        print '    },';
    }

    print '};';

    print STDERR 'File:  ' . $outfilename;
} #end sub writeSourceC

sub calcDataSize {
    my ($char_size) = @_;

    if($char_size->{'y'} <= 8) {
        return(8);
    } elsif($char_size->{'y'} > 8 && $char_size->{'y'} <= 16) {
        return(16);
    } elsif($char_size->{'y'} > 16 && $char_size->{'y'} <= 32) {
        return(32);
    } else {
        # really big, I will not judge you
        return(64);
    }
} #end sub calcDataSize

sub main {
    &help() if(! $ARGV[0]);

    my $char_size = {};
    my $formats = {};

    my $chars = &getFontData($ARGV[0],$char_size,$formats);
    &writeSourceC($chars,$char_size,$formats);
    #print STDERR Dumper($char_size);
}

sub help {
    print STDERR "Usage:  $0 <filename>.bdf";
    exit(1);
}
