EBDT と EBLC を削除する

どう見ても現実逃避&チラ裏です。本当に(ry

#!/usr/bin/perl

use strict;
use warnings;
use POSIX qw( ceil );

my $handle;
if (@ARGV) {
    open $handle, '<', $ARGV[0]
        or die $!;
}
else {
    $handle = \*STDIN;
}
binmode $handle;

my $src_header = read_header($handle);

my $dst_header = trans_header($src_header);

write_header(\*STDOUT, $dst_header);

copy_bodies(\*STDOUT, $dst_header, $handle, $src_header);

close $handle;

exit();

sub read_header {
    my ($src)  = @_;
    my $header = {};
    my $buf;

    read $src, $buf, 4  or  die $!;
    $header->{version} = $buf;

    read $src, $buf, 2+2+2+2  or  die $!;
    ($header->{numTables},
     $header->{searchRange},
     $header->{entrySelector},
     $header->{rangeShift})
        = unpack 'nnnn', $buf;

    my $numTables = $header->{numTables};

    $header->{table} = {};
    $header->{tags}  = [];

    while ($numTables -- > 0) {
        read $src, $buf, 4+4+4+4  or  die $!;
        my $tag = substr $buf, 0, 4, '';
        my ($checkSum, $offset, $length)
            = unpack 'NNN', $buf;

        $header->{table}->{$tag}
            = {
                tag      => $tag,
                checkSum => $checkSum,
                offset   => $offset,
                length   => $length,
            };

        push @{ $header->{tags} }, $tag;
    }

    return $header;
}

sub trans_header {
    my $header = clone_header($_[0]);

    $header->{numTables} -= 2;

    $header->{entrySelector}
        = ceil(log $header->{numTables} / log 2);

    $header->{searchRange}
        = (1 << $header->{entrySelector}) * 16;

    $header->{rangeShift}
        = $header->{numTables} * 16
          - $header->{searchRange};

    my $ebdt = $header->{table}->{EBDT};
    my $eblc = $header->{table}->{EBLC};

    die 'EBDT does not exist'
        if ! defined $ebdt || ! defined $eblc;

    $header->{tags}
        = [
            sort {
                $header->{table}->{$a}->{offset}
                    <=>
                $header->{table}->{$b}->{offset}
            }
            grep {
                $_ ne 'EBDT' && $_ ne 'EBLC'
            }
            @{ $header->{tags} }
          ];

    my $length
        = 4+2+2+2+2 + (4+4+4+4) * $header->{numTables};
    $header->{length} = ceil($length / 4) * 4;
    $header->{trail}  = $header->{length} - $length;

    my $offset = $header->{length};
    foreach my $tag (@{ $header->{tags} }) {
        my $table = $header->{table}->{$tag};

        $table->{offset} = $offset;

        $offset += $table->{length};
        $offset += 3 - ($offset + 3) % 4;
    }

    return $header;
}

sub clone_header {
    my ($src) = @_;
#   use Storable qw( dclone );
#   return dclone($_[0]);

    my $dst = { %$src };

    $dst->{tags} = [ @{ $src->{tags} } ];

    $dst->{table} = {};
    while (my ($tag, $table) = each %{ $src->{table} }) {
        $dst->{table}->{$tag} = { %$table };
    }

    return $dst;
}

sub write_header {
    my ($dst, $header) = @_;

    print {$dst} $header->{version};

    print {$dst}
        pack 'nnnn', $header->{numTables},
                     $header->{searchRange},
                     $header->{entrySelector},
                     $header->{rangeShift};

    foreach my $tag (@{ $header->{tags} }) {
        my $table = $header->{table}->{$tag};

        print {$dst} $tag;
        print {$dst}
            pack 'NNN', $table->{checkSum},
                        $table->{offset},
                        $table->{length};
    }

    print {$dst} "\x00" x $header->{trail};
}

sub copy_bodies {
    my ($dst_handle, $dst_header, $src_handle, $src_header) = @_;

    my $offset = tell $src_handle;

    foreach my $tag (@{ $dst_header->{tags} }) {
        my $table = $src_header->{table}->{$tag};

#       seek $src_handle, $table->{offset} - $offset, 1  or  die $!;
        skip_handle($src_handle, $table->{offset} - $offset);
        $offset = $table->{offset};

        copy_handle($dst_handle, $src_handle, $table->{length});
        $offset += $table->{length};

        my $trail = 3 - ($table->{length} + 3) % 4;
        print {$dst_handle} "\x00" x $trail;
    }
}

sub skip_handle {
    my ($src, $length) = @_;
    my $buf;
    my $unit = 4096;

    while ($length > 0) {
        $unit = $length if $length < $unit;

        read $src, $buf, $unit  or  die $!;

        $length -= $unit;
    }
}

sub copy_handle {
    my ($dst, $src, $length) = @_;
    my $buf;
    my $unit = 4096;

    while ($length > 0) {
        $unit = $length if $length < $unit;

        read $src, $buf, $unit  or  die $!;
        print {$dst} $buf;

        $length -= $unit;
    }
}

使い方は読めばわかりますが

% perl remove_ebdt.pl foobar.ttf > removed.ttf

結果については一切保証しません。

  • どう考えても C で書くべきだけど手元に環境ネー
  • ARGV ってハンドルとしての有用性はイマイチだけどスマートなやり方忘れた;要復習
  • non-seekable なのに対応するのがちょっと大変だった
  • 最初 4 byte alignment 忘れてたんでえらいめに