#!/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;