#! /bin/perl # # ttfar - TrueType font file archiver # # # References: Chapter 2 "TrueType font file" of "TrueType specifications" in # http://www.microsoft.com/typography/specs/default.htm # use Getopt::Std; use FileHandle; use Fcntl; #------------------------------------------------------------ package TableDirectoryEntry; sub new { my $type = shift; my $self = {}; bless $self, $type; $self->set(@_); $self; } sub set { my $self = shift; my %params = @_; for my $item qw( tag checkSum offset length ) { $self->{$item} = $params{$item} if exists $params{$item}; } } sub load { my $self = shift; my $file = shift; my $tag = shift; $self->{'file'} = $file; $self->{'tag'} = $tag; #print "$file\n"; open(FH, "< $file") or die "$!: $file , stopped"; read(FH, $self->{'data'}, (-s FH)); close(FH); $self->{'length'} = length($self->{'data'}); $self->{'padded'} = ($self->{'length'} + 3) & (~3); $self->{'pad'} = $self->{'padded'} - $self->{'length'}; #printf "length: %d\n", $self->{'length'}; #printf "pad : %d\n", $self->{'pad'}; if ($self->{'pad'} > 0) { $self->{'data'} .= "\x00" x $self->{'pad'}; } #printf "padded: %d\n", length($self->{'data'}); $self->CalcTableChecksum(); $self->{'length'}; } sub CalcTableChecksum { my $self = shift; my $sum = 0; my $length = length($self->{'data'}); for (my $start = 0; $length >= 4; $start += 4, $length -= 4) { $sum += unpack("N", substr($self->{'data'}, $start, 4)); } $self->{'checkSum'} = $sum & 0xffffffff; #printf "checkSum: %10d\n", $self->{'checkSum'}\n"; } sub getData { my $self = shift; $self->{'data'}; } #------------------------------------------------------------ package TableDirectory; sub new { my $type = shift; my $self = {}; $self->{'Entries'} = []; bless $self, $type; } sub read { my $self = shift; my $file = shift; $self->{'file'} = $file; open(FH, "< $file") or die "$!: $file , stopped"; my $buffer; read(FH, $buffer, 12); @{$self}{ qw ( sfnt_version_high sfnt_version_low numTables searchRange entrySelector rangeShift ) } = unpack("nnnnnn", $buffer); for (my $i = 0; $i < $self->{'numTables'}; $i++) { read(FH, $buffer, 4); my $tag = join('', map { chr } unpack("C4", $buffer)); read(FH, $buffer, 12); my ($checkSum, $offset, $length) = unpack("NNN", $buffer); my $entry = TableDirectoryEntry->new('tag' => $tag, 'checkSum' => $checkSum, 'offset' => $offset, 'length' => $length); push @{$self->{'Entries'}}, $entry; } close FH; } sub getEntries { my $self = shift; @{$self->{'Entries'}}; } sub add { my $self = shift; my $entry = shift; # TableDirectoryEntry push @{$self->{'Entries'}}, $entry; } sub CalcSearchRange { my $self = shift; my $numTables = $self->{'numTables'}; my $x; my $count = 0; return 0 if $numTables <= 0; for ($x = 1; ; $x *= 2, $count++) { last if ($x <= $numTables && $numTables < $x * 2); } $self->{'searchRange'} = $x * 16; $self->{'entrySelector'} = $count; $self->{'rangeShift'} = $numTables * 16 - $self->{'searchRange'}; } sub write { my $self = shift; my $fh = shift; $self->{'sfnt_version_high'} = 1; $self->{'sfnt_version_low'} = 0; $self->{'numTables'} = scalar(@{$self->{'Entries'}}); $self->CalcSearchRange(); my $buffer = pack("nnnnnn", @{$self}{ qw ( sfnt_version_high sfnt_version_low numTables searchRange entrySelector rangeShift ) }); $fh->print($buffer); my $offset = 12 + 16 * $self->{'numTables'}; for my $entry (@{$self->{'Entries'}}) { $entry->{'offset'} = $offset; $buffer = pack("C4", map { ord } split('', $entry->{'tag'})); $fh->print($buffer); $buffer = pack("NNN", @{$entry}{ qw ( checkSum offset length )}); $fh->print($buffer); $offset += length($entry->{'data'}); } } sub printVerbose { my $self = shift; printf "filename: %s", $self->{'file'}; #printf "===============\n"; #printf "Table Directory\n"; #printf "===============\n"; printf "\tsfnt version: %d.%d", $self->{'sfnt_version_high'}, $self->{'sfnt_version_low'}; printf "\tnumTables: %d", $self->{'numTables'}; printf "\tsearchRange: %d", $self->{'searchRange'}; printf "\tentrySelector: %d", $self->{'entrySelector'}; printf "\trangeShift: %d", $self->{'rangeShift'}; print "\n"; for my $entry (@{$self->{'Entries'}}) { printf "\ttag: %4s", $entry->{'tag'}; printf "\tcheckSum: %10d", $entry->{'checkSum'}; printf "\toffset: %10d", $entry->{'offset'}; printf "\tlength: %8d", $entry->{'length'}; print "\n"; } } sub printSimple { my $self = shift; printf "%s", $self->{'file'}; printf " %d.%d", $self->{'sfnt_version_high'}, $self->{'sfnt_version_low'}; printf "%8d", $self->{'numTables'}; printf "%8d", $self->{'searchRange'}; printf "%8d", $self->{'entrySelector'}; printf "%8d", $self->{'rangeShift'}; print "\n"; for my $entry (@{$self->{'Entries'}}) { printf "\t%4s", $entry->{'tag'}; printf "\t%10d", $entry->{'checkSum'}; printf "\t%8d", $entry->{'offset'}; printf "\t%8d", $entry->{'length'}; print "\n"; } } sub print { my $self = shift; my $flag = shift; if ($flag) { $self->printVerbose(); } else { $self->printSimple(); } } #------------------------------------------------------------ package TTFFile; sub new { my $type = shift; my $self = {}; bless $self, $type; } sub open { my $self = shift; my $file = shift; $self->{'file'} = $file; my $fh = new FileHandle; $fh->open("< $file") or die "$!: $file , stopped"; $self->{'fh'} = $fh; } sub close { my $self = shift; $self->{'fh'}->close; } sub getData { my $self = shift; my $entry = shift; # TableDirectoryEntry my $buffer; my $fh = $self->{'fh'}; #print "file: $self->{'file'}\t"; #print "offset: $entry->{'offset'}\t"; #print "length: $entry->{'length'}\n"; $fh->seek($entry->{'offset'}, SEEK_SET); $fh->read($buffer, $entry->{'length'}); $buffer; } sub create { my $self = shift; my $file = shift; my $directory = shift; # TableDirectory my $fh = new FileHandle; $fh->open("> $file") or die "$!: $file , stopped"; $self->{'fh'} = $fh; $directory->write($fh); for my $entry ($directory->getEntries()) { my $table = $entry->getData(); $fh->print($table); } $fh->close(); } #------------------------------------------------------------ package main; sub ttf_list { my $file = shift; my $directory = TableDirectory->new(); $directory->read($file); $directory->print($opt_v); } sub ttf_extract { my $file = shift; my $directory = TableDirectory->new(); $directory->read($file); my $ttf = TTFFile->new(); $ttf->open($file); for my $entry ($directory->getEntries()) { my $table = $ttf->getData($entry); printf "%s %8d\n", $entry->{'tag'}, $entry->{'length'} if $opt_v; my $tag = $entry->{'tag'}; $tag =~ s{/}{~}g; $tag =~ s{ }{_}g; #print "$tag\n"; open FH, "> $tag" or die "$!: $tag , stopped"; print FH $table; close FH; } $ttf->close(); } sub ttf_create { my $file = shift; my $directory = TableDirectory->new(); for my $item (@ARGV) { my $tag = $item; $tag =~ s{~}{/}g; $tag =~ s{_}{ }g; print "$tag\t" if $opt_v; my $entry = TableDirectoryEntry->new(); $entry->load($item, $tag); printf "\tcheckSum: %10d", $entry->{'checkSum'} if $opt_v; printf "\tlength: %8d", $entry->{'length'} if $opt_v; printf "\tpad: %1d", $entry->{'pad'} if $opt_v; $directory->add($entry); print "\n" if $opt_v; } my $ttf = TTFFile->new(); $ttf->create($file, $directory); } sub usage { print <