#!/usr/local/bin/perl
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../../Parse-Binary/lib";
use strict;
use DBI;
use DBD::SQLite;
use Data::Dumper;
use Parse::AFP;
use Getopt::Long;
use File::Glob 'bsd_glob';
my %CodePages = (
947 => {
FillChar => "\xA1\x40",
FirstChar => "\xA4\x40",
CharPattern => qr{
(
[\x81-\xA0\xC7-\xC8\xFA-\xFE]. # UDC range 1
|
\xC6[\xA1-\xFE] # UDC range 2
)
|
([\x00-\x7f]) # Single Byte
|
(..) # Double Byte
}x,
NoUDC => qr{^
(?:
[\x00-\x7f]+
|
(?:[\xA1-\xC5\xC9-\xF9].)+
|
(?:\xC6[^\xA1-\xFE])+
)*
$}x,
},
835 => {
FillChar => "\x40\x40",
FirstChar => "\x4C\x41",
CharPattern => qr{
([\x92-\xFE].) # UDC
|
((?!)) # Single Byte
|
([\x40-\x91].) # Double Byte
}x,
NeedDBCSPattern => 1,
NoUDC => qr{^[^\x92-\xFE]*$}x,
},
);
my ($dbcs_pattern, @db);
my $codepage = 947;
my $dir = 'udcdir';
my $adjust;
our $input;
our $output = 'fixed.afp';
GetOptions(
'i|input:s' => \$input,
'f|fontdb:s@' => \@db,
'o|output:s' => \$output,
'u|udcdir:s' => \$dir,
'd|dbcs-pattern:s' => \$dbcs_pattern,
'c|codepage:i' => \$codepage,
'a|adjust' => \$adjust,
);
$input ||= shift;
@db = sort grep /\.f?db$/i, map { (-d $_) ? bsd_glob("$_/*") : $_ } (@db ? @db : 'fonts.db');
die "Usage: $0 [-a] [-c 947|835] -d dbcs_pattern -i input.afp -o output.afp -f fonts.db\n"
if !@db or grep !defined, $input, $output;
$CodePages{$codepage} or die "Unknown codepage: $codepage";
our ($FillChar, $FirstChar, $CharPattern, $NeedDBCSPattern, $NoUDC)
= @{$CodePages{$codepage}}{qw( FillChar FirstChar CharPattern NeedDBCSPattern NoUDC )};
die "Need DBCS Pattern with -d for this codepage"
if $NeedDBCSPattern and !$dbcs_pattern;
my (%FontToId, %IdToFont);
##########################################################################
no warnings qw(once numeric);
my %errors;
my $db = shift(@db);
die "No such database: $db" unless -e $db;
my $dbh = DBI->connect(
"dbi:SQLite:dbname=$db", '', '', {
PrintError => 0,
HandleError => sub { $errors{$_[0]}++ },
}
) or die $DBI::errstr;
my $fonts = $dbh->selectall_hashref("SELECT * FROM Fonts", 'FontName') or die $dbh->errstr;
foreach my $idx (0..$#db) {
my $filename = $dbh->quote($db[$idx]);
$dbh->do("ATTACH DATABASE $filename AS DB$idx") or die $dbh->errstr;
my $more_fonts = $dbh->selectall_hashref("SELECT * FROM Fonts", 'FontName') or die $dbh->errstr;
%$fonts = (%$fonts, %$more_fonts);
}
##########################################################################
# SPLIT!
#
udcsplit::run();
##########################################################################
sub udc4pca {
my ($in, $out) = @_;
my $afp = Parse::AFP->new($in, {lazy => 1, output_file => $out});
$afp->callback_members([qw( MCF1 MCF PGD PTX EMO EPG * )]);
}
##########################################################################
my @UDC;
sub __ {
$_[0]->done;
}
my ($x, $y);
my ($XUnit, $YUnit, $XPageSize, $YPageSize);
sub PGD {
my $rec = shift;
$XUnit = $rec->XLUnitsperUnitBase;
$YUnit = $rec->YLUnitsperUnitBase;
$XPageSize = $rec->XPageSize;
$YPageSize = $rec->YPageSize;
$rec->done;
$x = $y = 0;
}
sub MCF1 {
my $rec = shift;
my $font_e = substr($rec->CodedFontName, 2, 4);
my $font_eid = $rec->CodedFontLocalId;
$FontToId{$font_e} = $font_eid;
$IdToFont{$font_eid} = $font_e;
$rec->done;
}
sub MCF {
my $rec = shift;
$rec->callback_members(['MCF::DataGroup']);
$rec->done;
}
sub MCF_DataGroup {
my $data_group = shift;
$data_group->callback_members(['Triplet::FQN', 'Triplet::RLI']);
}
{
my $font_e;
sub Triplet_FQN {
my $fqn = shift;
$font_e = $fqn->Data;
}
sub Triplet_RLI {
my $rli = shift;
my $font_eid = $rli->Data;
$FontToId{$font_e} = $font_eid;
$IdToFont{$font_eid} = $font_e;
}
}
sub PTX {
my ($rec, $buf) = @_;
my $font_eid;
# Now iterate over $$buf.
my $pos = 11;
my $len = length($$buf);
while ($pos < $len) {
my ($size, $code) = unpack("x${pos}CC", $$buf);
$size or do {
open my $fh, '>:raw', 'buf.afp';
print $fh $$buf;
close $fh;
die "Incorrect parsing: $pos\n";
};
if ($code == 0xDA or $code == 0xDB) {
if (substr($$buf, $pos + 2, $size - 2) !~ $NoUDC) {
$rec->callback_members([map "PTX::$_", qw(
SIM SBI STO SCFL AMI AMB RMI RMB BLN TRN
)], \$font_eid);
$rec->refresh;
last;
}
}
$pos += $size;
}
$rec->done;
}
sub PTX_AMI {
my $rec = shift;
$x = $rec->Data;
}
sub PTX_AMB {
my $rec = shift;
$y = $rec->Data;
}
sub PTX_RMI {
my $rec = shift;
$x += $rec->Data;
}
sub PTX_RMB {
my $rec = shift;
$y += $rec->Data;
}
my $InlineMargin;
sub PTX_SIM {
my $rec = shift;
$InlineMargin = $rec->Data;
}
my $BaselineIncrement;
sub PTX_SBI {
my $rec = shift;
$BaselineIncrement = $rec->Data;
}
sub PTX_BLN {
my $rec = shift;
$x = $InlineMargin;
$y += $BaselineIncrement;
}
my ($XOrientation, $YOrientation);
sub PTX_STO {
my $rec = shift;
$XOrientation = $rec->Orientation;
$YOrientation = $rec->WrapDirection;
}
sub PTX_SCFL {
my ($dat, $font_ref) = @_;
$$font_ref = $dat->Data;
}
my %Increment;
sub PTX_TRN {
my ($dat, $font_ref) = @_;
my $font_eid = $$font_ref;
my $font_name = $IdToFont{$font_eid};
$font_name =~ s/^X\d/X0/;
my $string = $dat->Data;
my $data = '';
# if $font_name is single byte...
# simply add increments together without parsing UDC
if ($dbcs_pattern and $font_name !~ /$dbcs_pattern/o) {
$Increment{$font_name} ||= { @{
$dbh->selectcol_arrayref(
"SELECT Character, Increment FROM $font_name",
{ Columns=>[1, 2] }
) || [] } };
$x += $Increment{$font_name}{$_}
or die "Cannot find char ".unpack('(H2)*', $_)." in $font_name"
foreach split(//, $string);
return;
}
# my $dbcs_space_char = "\xFA\x40";
while ($string =~ /$CharPattern/go) {
# ... calculate position, add to fonts to write ...
if ( $1 || $3 ) {
$Increment{$font_name} ||= { @{
$dbh->selectcol_arrayref(
"SELECT Character, Increment FROM $font_name",
{ Columns => [1, 2] }
) || [] } };
}
if (defined $1) {
push @UDC, {
X => $x,
Y => $y,
Character => $1,
FontName => $font_name
};
$data .= $FillChar;
$x += $Increment{$font_name}{$1};
}
elsif (defined $2) {
# single byte
$Increment{$font_name} ||= { @{
$dbh->selectcol_arrayref(
"SELECT Character, Increment FROM $font_name",
{ Columns=>[1, 2] }
) || [] } };
$x += $Increment{$font_name}{$2}
or die "Cannot find char ".unpack('(H2)*', $2)." in $font_name";
$data .= $2;
#print $font_name, "=", $x, "\n";
}
else {
$data .= $3;
$x += $Increment{$font_name}{$3} || $Increment{$font_name}{$FirstChar};
}
}
$dat->{struct}{Data} = $data;
}
BEGIN { *EMO = *EPG; }
sub EPG {
my $rec = shift;
if (!@UDC) {
$rec->done;
return;
}
# ... write out the actual BII..IOC..IID..ICP..IRD..EII images ...
#print "Writing out Bitmap...\n" if @UDC;
# Construct:
$rec->spawn_obj(
Class => 'BII',
Data => 'UDCImage',
)->write;
$rec->spawn_obj(
Class => 'IOC',
ConstantData1 => ("00" x 8),
ConstantData2 => ("FF" x 2),
Reserved1 => '00',
Reserved2 => '00',
XMap => '03e8',
XOffset => 0,
XOrientation => $XOrientation,
YMap => '03e8',
YOffset => 0,
YOrientation => $YOrientation,
)->write;
my %res = @{$dbh->selectcol_arrayref(
"SELECT FontName, Resolution FROM Fonts", { Columns => [1,2] }
)};
my $name = $UDC[0]{FontName};
$name =~ s/\s//g;
my $res = $res{$name};
$rec->spawn_obj(
Class => 'IID',
Color => '0008',
ConstantData1 => '000009600960000000000000',
ConstantData2 => '000000002D00',
ConstantData3 => '00',
XBase => '00',
XCellSizeDefault => 0,
XSize => 0,
XUnits => $res,
YBase => '00',
YCellSizeDefault => 0,
YSize => 0,
YUnits => $res,
)->write;
foreach my $char (@UDC) {
my $sth = $dbh->prepare("SELECT * FROM $char->{FontName} WHERE Character = ?") or next;
$sth->execute($char->{Character});
my $row = $sth->fetchrow_hashref or next;
my ($X, $Y) = @{$char}{qw( X Y )};
$X += $row->{ASpace};
my $oset = $row->{BaseOffset};
$oset = int($oset * 3 / 4) if $adjust;
$Y -= $oset;
if ($YOrientation eq '5a00') {
($X, $Y) = ($XPageSize - $Y, $X);
}
$rec->spawn_obj(
Class => 'ICP',
XCellOffset => $X,
XCellSize => $row->{Width},
XFillSize => $row->{Width},
YCellOffset => $Y,
YCellSize => $row->{Height},
YFillSize => $row->{Height},
)->write;
$rec->spawn_obj(
Class => 'IRD',
ImageData => $row->{Bitmap},
)->write;
}
$rec->spawn_obj(
Class => 'EII',
Data => 'UDCImage',
)->write;
@UDC = ();
$rec->done;
}
1;
package udcsplit;
my ($has_udc, $name, $prev, $has_BNG, $PTX_cnt);
my ($itmp, $otmp, $ifh, $ofh, $ipos, $opos);
use strict;
sub run {
*Parse::AFP::Record::new = sub {
my ($self, $buf, $attr) = @_;
if (substr($$buf, 3, 3) eq "\xD3\xEE\x9B") { return bless($buf, 'PTX'); }
# if (substr($$buf, 3, 3) eq "\xD3\xA8\xAD") { return bless($buf, 'BNG'); }
if (substr($$buf, 3, 3) eq "\xD3\xA8\xAF") { return bless($buf, 'BPG'); }
if (substr($$buf, 3, 3) eq "\xD3\xA8\xDF") { return bless($buf, 'BMO'); }
return $self->Parse::Binary::new($buf, $attr);
};
*PTX::done = sub { return };
*BPG::done = sub { return };
*BMO::done = sub { return };
*PTX::callback = sub { udcsplit::PTX($_[0]) };
*BPG::callback = sub { udcsplit::BPG($_[0]) };
*BMO::callback = sub { udcsplit::BMO($_[0]) };
$name = $prev = 0;
$ipos = $opos = 0;
($itmp, $otmp) = ("input-$$.afp", "output-$$.afp");
my $afp = Parse::AFP->new($main::input, { lazy => 1, output_file => $main::output });
($ifh, $ofh) = @{$afp}{qw( input output )};
$afp->callback_members([qw( BMO BPG PTX * )]);
begin_page(0);
}
sub begin_page {
$prev = $name; $name++;
my $pos = tell($ifh) - $_[0];
udc4pca($pos) if $has_udc;
$has_udc = 0;
$ipos = $pos;
$opos = tell($ofh);
# print "ipos is now $ipos; opos is now $opos\n";
}
sub udc4pca {
if (my $pid = fork) {
waitpid($pid, 0);
($? == 0) or die $?;
print STDERR '.';
}
else {
close $ifh;
close $ofh;
my $size = ($_[0] - $ipos);
open my $nfh, '<:raw', $main::input or die $!;
seek $nfh, $ipos, 0;
open my $fh, '>:raw', $itmp or die $!;
{
local $/ = \$size;
print $fh scalar <$nfh>;
}
close $fh;
no warnings 'redefine';
*Parse::AFP::Record::new = \&Parse::Binary::new;
undef &PTX::callback;
undef &PTX::done;
main::udc4pca($itmp => $otmp);
exit;
}
seek $ofh, $opos, 0;
open my $fh, '<:raw', $otmp or die $!;
local $/ = \32768;
while (<$fh>) {
print $ofh $_;
}
close $fh;
unlink ("input-$$.afp", "output-$$.afp");
}
sub BNG {
$has_BNG = 1;
begin_page(length ${$_[0]});
$_[0]->done;
}
BEGIN { *BMO = *BPG; }
sub BPG {
begin_page(length ${$_[0]});
$_[0]->done;
}
sub PTX {
my $rec = my $buf = shift;
return $rec->done if $has_udc;
# Now iterate over $$buf.
my $pos = 11;
my $len = length($$buf);
while ($pos < $len) {
my ($size, $code) = unpack("x${pos}CC", $$buf);
$size or do {
open my $fh, '>:raw', 'buf.afp';
print $fh $$buf;
close $fh;
die "Wrong parsing: $pos\n";
};
if ($code == 0xDA or $code == 0xDB) {
if ( substr($$buf, $pos + 2, $size - 2) !~ /$main::NoUDC/o) {
$has_udc = 1;
last;
}
}
$pos += $size;
}
$rec->done;
}
sub __ { $_[0]->done }
1;