#!/usr/bin/perl use IO::File; use Encode::Unicode; use Pod::Usage; use Getopt::Std; use Encode; getopts('hr:'); unless ($ARGV[0] || $opt_h) { pod2usage(1); exit; } if ($opt_h) { pod2usage(-verbose => 2, -noperldoc => 1); exit; } %parser = ( 'Encoding' => sub { my ($str, $currchar) = @_; my (@vals) = split(' ', $str); $currchar->{'gnum'} = $vals[2]; return undef; }); $base = Font::TTF::Scripts::SFD->new(%parser); $local = Font::TTF::Scripts::SFD->new(%parser); $other = Font::TTF::Scripts::SFD->new(%parser); $base->parse_file($ARGV[0], $base); $local->parse_file($ARGV[1], $local); $other->parse_file($ARGV[2], $other); #@basechars = sort {$a->{'gnum'} <=> $b->{'gnum'}} @{$base->{'glyphs'}}; #@localchars = sort {$a->{'gnum'} <=> $b->{'gnum'}} @{$local->{'glyphs'}}; #@otherchars = sort {$a->{'gnum'} <=> $b->{'gnum'}} @{$other->{'glyphs'}}; @basechars = order_glyphs($base->{'glyphs'}); @localchars = order_glyphs($local->{'glyphs'}); @otherchars = order_glyphs($other->{'glyphs'}); merge_items($base, $local, $other); for ($i = 0; $i < @basechars; $i++) { merge_items($basechars[$i], $localchars[$i], $otherchars[$i]); } $maxnum = scalar @basechars; push(@basechars, @localchars[$maxnum .. $#localchars]); push(@basechars, @otherchars[$maxnum .. $#otherchars]); for ($i = 0; $i < @basechars; $i++) { $g = $basechars[$i]; $g->{'gnum'} = $i; $g->{'lines'}[$g->{'commands'}{'Encoding'}[0]] =~ s/^(.*?)(\d+)/$1 . $i/oe; } $base->{'glyphs'} = \@basechars; $ofh = IO::File->new("> $ARGV[3]") || die "Can't open $ARGV[3] for writing"; $base->print_font($base, $ofh); $ofh->close(); sub add_char { my ($base, $char) = @_; my ($newind) = scalar @{$base->{'glyphs'}}; push (@{$base->{'glyphs'}}, $char); $char->{'lines'}[$char->{'commands'}{'Encoding'}[0]] =~ s/\s(\d+)$/ $newind/o; } sub merge_items { my ($base, $local, $other) = @_; my ($c); foreach $c (@{$base->{'commindex'}}) { my ($cb) = $base->{'commands'}{$c->[0]}[$c->[1]]; my ($cl) = $local && $local->{'commands'}{$c->[0]}[$c->[1]]; my ($co) = $other && $other->{'commands'}{$c->[0]}[$c->[1]]; my ($lb) = $cb && $base->{'lines'}[$cb]; my ($ll) = $cl && $local->{'lines'}[$cl]; my ($lo) = $co && $other->{'lines'}[$co]; if ($lb eq $ll) { $base->{'lines'}[$cb] = $lo if ($lb ne $lo); } elsif ($lb ne $lo) { $base->{'lines'}[$cb] = ($opt_r eq 'o' or !defined($ll)) ? $lo : (($opt_r eq 'l' and defined ($ll)) ? $ll : $lb); } else { $base->{'lines'}[$cb] = $ll; } } if ($local) { foreach $c (@{$local->{'commindex'}}) { next if (defined $base->{'commands'}{$c->[0]} && scalar @{$base->{'commands'}{$c->[0]}} > $c->[1]); insert_line($base, $local, $other, $c, $cold); } continue { $cold = $c; } } if ($other) { foreach $c (@{$other->{'commindex'}}) { next if (defined $base->{'commands'}{$c->[0]} && scalar @{$base->{'commands'}{$c->[0]}} > $c->[1]); insert_line($base, $local, $other, $c, $cold); } continue { $cold = $c; } } } sub insert_line { my ($base, $local, $other, $c, $cold) = @_; my $cl = $local->{'commands'}{$c->[0]}[$c->[1]]; my $cb = $base->{'commands'}{$cold->[0]}[-1]; my $co = $other->{'commands'}{$c->[0]}[$c->[1]]; my $ll = $cl && $local->{'lines'}[$cl]; my $lo = $co && $other->{'lines'}[$co]; if ($ll ne $lo and defined ($lo)) { $base->{'lines'}[$cb] .= $opt_r eq 'o' ? $lo : $ll; } else { $base->{'lines'}[$cb] .= $ll; } push (@{$base->{'commands'}{$c->[0]}}, $cb); } sub order_glyphs { my ($glyphs) = @_; my (@res, $g); my ($max) = scalar(@{$glyphs}); foreach $g (@{$glyphs}) { if (defined $res[$g->{'gnum'}]) { $res[$max++] = $g } else { $res[$g->{'gnum'}] = $g; } } return @res } package Font::TTF::Scripts::SFD; use IO::File; sub new { my ($class, %info) = @_; my ($self) = {%info}; return bless $self, ref $class || $class; } sub parse_file { my ($self, $fname, $base) = @_; my ($fh); my ($command, $text); my %modes = ( 'TtTable' => 'EndTTInstrs', 'TtInstrs' => 'EndTTInstrs', 'Image' => 'EndImage', 'TtfInstrs' => 'EndTtf', 'ChainSub2' => 'EndFPST', 'ChainPos2' => 'EndFPST', 'ContextSub2' => 'EndFPST', 'ContextPos2' => 'EndFPST', 'ReverseChain2' => 'EndFPST', 'ShortTable' => 'EndShort', 'SplineSet' => 'EndSplineSet' ); my %singles = map {$_ => 1 } qw(Fore Back); if (ref $fname) { $fh = $fname; } else { $fh = IO::File->new("< $fname") || die "Can't open $fname for reading"; } while (<$fh>) { my ($res); if ($_ =~ m/^[\s\d"]/o || $mode) { $text .= $_; if ($_ =~ m/^$mode/) { $mode = ''; } next; } elsif (defined $self->{$command}) { my ($t) = $text; $t =~ s/^\s*//o; $res = &{$self->{$command}}($t, $base); $base = $res if ($res); } if ($command) { my ($commstr) = $command; if ($text =~ m/^\s*$/o || $command eq 'SplineSet') { } elsif ($modes{$command}) { $commstr .= ":"; } elsif ($text =~ m/\n.+\n/o) { } else { $commstr .= ":"; } if ($command eq 'StartChar') { $text =~ s/\s*$//o; $text =~ s/^\s*//o; my $nbase = {'post' => $text, 'PSName' => $text, 'parent' => $base}; push (@{$base->{'glyphs'}}, $nbase); $base = $nbase; $text = " $text\n"; } elsif ($command eq 'EndChars') { $base->{'final'} = {'base' => $base}; $base = $base->{'final'}; } push (@{$base->{'lines'}}, "$commstr$text"); push (@{$base->{'commands'}{$command}}, scalar @{$base->{'lines'}} - 1); push (@{$base->{'commindex'}}, [$command, scalar @{$base->{'commands'}{$command}} - 1]); if ($command eq 'EndChar') { $base = $base->{'parent'} if defined ($base->{'parent'}); } elsif ($command eq 'SplineSet') { my ($line) = pop(@{$base->{'lines'}}); $base->{'lines'}[-1] .= $line; pop (@{$base->{'commindex'}}); pop (@{$base->{'commands'}{$command}}); } $command = ''; $text = ''; } if (s/^([^\s:]+)://o or $singles{$_}) { $command = $1 || $_; $text = $_ || "\n"; $mode = $modes{$command}; } else { $command = $_; $command =~ s/(\s*)$//o; $mode = $modes{$command}; $text = $1; } } if (defined $self->{$command}) { &{$self->{$command}}($text); } push (@{$base->{'lines'}}, "$command$text"); push (@{$base->{'commands'}{$command}}, scalar @{$base->{'lines'}}); } sub print_font { my ($self, $font, $fh) = @_; my ($g, $l); foreach $l (@{$font->{'lines'}}) { $fh->print($l); } foreach $g (@{$font->{'glyphs'}}) { foreach $l (@{$g->{'lines'}}) { $fh->print($l); } } if (defined $font->{'final'}) { foreach $l (@{$font->{'final'}{'lines'}}) { $fh->print($l); } } } __END__ =head1 TITLE sfdmeld - merges sfd files =head1 SYNOPSIS sfdmerge [-r winner] base local other Does a 3 way ancestral merge of sfd files. =head1 OPTIONS -h print manpage -r winner o => other wins clashes, l => local wins clashes default base wins clashes =head1 SEE ALSO sfdmeld =cut