#!/usr/bin/perl use GD; use strict; use warnings; use Getopt::Long; $|=1; ### ### Configure me ### my $charlist; for(32..126){ $charlist.=chr $_; }; # Runtime Options my ($verbose,$raw); my $size=17; my $font="../ttf/Ubuntu-Regular.ttf"; GetOptions ("size=i" => \$size, # numeric "font=s" => \$font, # string "verbose" => \$verbose, # flag "raw" => \$raw, # flag ); ### ### Code starts here. ### my $width=2000; my $height=100; my $xoff=30; my $origsize; my $c1size; my $c2size; my $title=getfontname($font); die "Couldn't get font name?" if !defined $title; $title.=" ${size}pt"; my $fonts=$title; $fonts=~s/ //g; $fonts=~s/Bitstream//; $fonts=~s/Sans//; my $file=$fonts; $file=~s/pt$//; $file=~y/A-Z/a-z/; $file.="-raw" if($raw); print "Rasterizing $title into ${file}.c\n"; ### Get & optimize bounding box my $im = new GD::Image($width,$height); my $white = $im->colorAllocate(255,255,255); my $black = $im->colorAllocate(0,0,0); my @bounds = $im->stringFT(-$black, $font, $size, 0, 0, $xoff,$charlist); my ($mx,$my)=($bounds[2],$bounds[3]); my ($top,$bottom)=($bounds[7],$my); die "Increase width" if $mx>$width; die "Increase height" if $my>$width; die "Increase xoff" if $bounds[7]<0; my $found; # Cut whitespace at top do { $found=0; for my $x (0..$mx){ if( $im->getPixel($x,$top) == 1){ $found=1;last; } }; $top++; }while($found==0); $top--; # Cut whitespace at bottom. do { $found=0; for my $x (0..$mx){ if( $im->getPixel($x,$bottom) == 1){ $found=1;last; } }; $bottom--; }while($found==0); $bottom++; my $pxsize=$bottom-$top+1; my $byte=int(($pxsize-1)/8)+1; # Round up print "Removed ",$top-$bounds[7],"px at top\n"; print "Removed ",$my-$bottom,"px at bottom\n"; print "Chars are ",$bottom-$top+1,"px ($byte bytes) high\n"; #print "x: $bounds[6] - $bounds[2]\n"; #print "y: $bounds[7] - $bounds[3]\n"; open (C,">",$file.".c")||die; open (H,">",$file.".h")||die; print C <colorAllocate(255,255,255); $black = $im->colorAllocate(0,0,0); @bounds = $im->stringFT(-$black, $font, $size, 0, 0, $xoff,$char.$charlist); my @char; for my $y ($top..$bottom){ for my $x (0..($bounds[2]-$mx)){ my $px= $im->getPixel($x,$y); $char[$x].=$px; # $px=~y/01/ */; print $px; }; # print "<\n"; }; print "### Start $char\n" if($verbose); print C " /* Char ",ord $char," is ",scalar@char,"px wide \@ $offset */\n"; $maxsz=scalar@char if scalar@char > $maxsz; $origsize+=$byte * scalar @char; # Whoops. Characters are upside down. for (@char){ $_=reverse $_; }; ### PK compression my @enc=do_pk(\@char); $c2size+=scalar(@enc); ### Lame compression # "compress" blank spaces my $preblank=0; while(defined $char[1] && $char[0]=~/^0+$/){ shift@char; $preblank++; }; my $postblank=0; while($#char>0 && $char[$#char]=~/^0+$/){ $#char--; $postblank++; }; $c1size+=$byte*scalar@char; my @raw; ### Raw character data for (@char){ my $h= pack("B*",$_).(chr(0).chr(0)); for my $b (1..$byte){ push @raw,ord(substr($h,$b-1,1)); }; }; # Maintenance overhead $origsize+=1; $c1size+=3; $c2size+=1; # If encoding is bigger, fall back to original char if($#enc>$#raw+3){ warn "Compression failure: Encoding char $char raw.\n"; $raw=1; }; # Generate C source if($raw){ @enc=(255,$preblank,$postblank,@raw); my @out=@enc; printf C " 0x%02x, %2d, %2d, /* rawmode, preblank, postblank */\n", (shift@out), (shift@out), (shift@out); for (@char){ print C " "; printf C "0x%02x, ",shift@out for(1..$byte); $_=~y/01/ */; print C " /* $_ */ \n"; }; }else{ for (@char){ $_=~y/01/ */; print C " /* $_ */ \n"; }; my $pretty=0; for(@enc){ print C " " if($pretty==0); printf C "0x%02x, ",$_; if(++$pretty==8){ print C "\n" ; $pretty=0; }; }; }; print C "\n"; push @offsets,sprintf " {%2d}, /* %s */\n",scalar(@enc),$char; print C "\n"; $offset+=scalar(@enc); if($verbose){ print "-"x80,"\n"; }; }; print C <>4; }; for my $undef (1..$#packed){ unshift @packed,0; }; return @packed; }; ### ### Encode RLE data (with included repeat counts) into a nybble stream ### ### PK has "dyn" per-character, but for our font size encoding a dyn per ### character needs more space than it saves, so it's fixed for now. ### sub pk_encode { my @out=@_; my $dyn=12; my @enc; for (@out){ if($_ =~ /\[(\d+)\]/ ){ if($1 == 1){ push @enc,15; }else{ my $n=$1-1; # this deviates from PK spec, i think. push @enc,14,pk_encode_long($1-1); }; }elsif($_ == 0){ # Encoding a 0 will only happen at the start of # character if "first pixel" is 1 instead of 0. # HACK: We transmit this fact to the decoder # by encoding a "14"-nibble which would be # illegal at this point anyway. push @enc,14; }elsif($_ <= $dyn){ # Short length push @enc,$_; }elsif($_ <= 16*(13-$dyn)+$dyn){ # Medium length my $b=($_-$dyn-1)&0x0f; my $a=(($_-$dyn-1)>>4)+$dyn+1; push @enc,$a,$b; # (16*($a-$dyn-1)+$b+$dyn+1 }else{ # long length my $n=$_- (16*(13-$dyn)+$dyn) + 16; push @enc,pk_encode_long($n); }; }; # print "enc: ",join(",",@enc),"\n"; return @enc; }; sub make_bytes{ my @enc=@_; my @out; while(@enc){ push @enc,0 if($#enc==2); push @out,16*(shift@enc)+(shift@enc); }; return @out; }; sub do_pk { my $char=shift; my $size=scalar @$char * $byte; print "Input char is $size bytes\n" if $verbose; $char=pk_dedup($char); if($verbose){ for (@$char){ print "dedup: $_\n"; }; }; my @rle=pk_rle ($char); if($verbose){ print "RLE: ",join(",",@rle),"\n"; }; my @enc=pk_encode (@rle); if($verbose){ print "encoded stream: ",join(",",@enc),"\n"; }; return make_bytes(@enc); }; sub getfontname { my $file = shift; use constant SEEK_SET => 0; use Encode qw(decode); open (my $fh,"<",$file) || die "Can't open $font: $!"; my($buf,$str); sysread($fh,$buf,12); # OFFSET_TABLE my($maj,$min,$num,undef,undef,undef)=unpack("nnnnnn",$buf); die "It's not a truetype font!" if ($maj != 1 || $min != 0); for(1..$num){ sysread($fh,$buf,16); # TABLE_DIRECTORY my($name,undef,$off1,$len)=unpack("A4NNN",$buf); if ($name eq "name"){ seek($fh,$off1,SEEK_SET); sysread($fh,$buf,6); my(undef,$cnt,$off2)=unpack("nnn",$buf); sysread($fh,$buf,12*$cnt); while(length($buf)){ my(undef,$enc,undef,$id,$len,$off3)=unpack("nnnnnn",$buf); substr($buf,0,12)=""; seek($fh,$off1+$off2+$off3,SEEK_SET); sysread($fh,$str,$len); if($enc==1){ $str=decode("UCS-2",$str); }; # 0: Copyright, 1: Name, ... if ($id == 4){ return $str; }; # print "- $str\n"; }; last; }; }; return undef; };