#!/usr/bin/perl use Parse::RecDescent; use constant BIN_OP => 1; use constant FUNCTION_CALL => 2; use vars qw($GRAMMAR); $GRAMMAR = <<END; expression : subexpression /^\$/ { \$return = \$item[1]; } subexpression : binary_op { \$item[1] } | function_call { \$item[1] } | var { \$item[1] } | literal { \$item[1] } | '(' subexpression ')' { \$item[2] } | <error> binary_op : '(' subexpression op subexpression ')' { [ \$item[3][0], \$item[3][1], \$item[2], \$item[4] ] } op : />=?|<=?|!=|==/ { [ ${\BIN_OP}, \$item[1] ] } | /le|ge|eq|ne|lt|gt/ { [ ${\BIN_OP}, \$item[1] ] } | /\\|\\||or|&&|and/ { [ ${\BIN_OP}, \$item[1] ] } | /[-+*\\/\%]/ { [ ${\BIN_OP}, \$item[1] ] } function_call : function_name '(' args ')' { [ ${\FUNCTION_CALL}, \$item[1], \$item[3] ] } | function_name ...'(' subexpression { [ ${\FUNCTION_CALL}, \$item[1], [ \$item[3] ] ] } | function_name '(' ')' { [ ${\FUNCTION_CALL}, \$item[1] ] } function_name : /[A-Za-z_][A-Za-z0-9_]*/ { \$item[1] } args : <leftop: subexpression ',' subexpression> var : /[A-Za-z_][A-Za-z0-9_]*/ { \\\$item[1] } literal : /-?\\d*\\.\\d+/ { \$item[1] } | /-?\\d+/ { \$item[1] } | <perl_quotelike> { \$item[1][2] } END # create global parser use vars qw($PARSER); $PARSER = Parse::RecDescent->new($GRAMMAR); # initialize preset function table use vars qw(%FUNC); %FUNC = ( 'sprintf' => sub { sprintf(shift, @_); }, 'substr' => sub { return substr($_[0], $_[1]) if @_ == 2; return substr($_[0], $_[1], $_[2]); }, 'lc' => sub { lc($_[0]); }, 'lcfirst' => sub { lcfirst($_[0]); }, 'uc' => sub { uc($_[0]); }, 'ucfirst' => sub { ucfirst($_[0]); }, 'length' => sub { length($_[0]); }, 'defined' => sub { defined($_[0]); }, 'abs' => sub { abs($_[0]); }, 'atan2' => sub { atan2($_[0], $_[1]); }, 'cos' => sub { cos($_[0]); }, 'exp' => sub { exp($_[0]); }, 'hex' => sub { hex($_[0]); }, 'int' => sub { int($_[0]); }, 'log' => sub { log($_[0]); }, 'oct' => sub { oct($_[0]); }, 'rand' => sub { rand($_[0]); }, 'sin' => sub { sin($_[0]); }, 'sqrt' => sub { sqrt($_[0]); }, 'srand' => sub { srand($_[0]); }, 'glyph' => sub { $_[0]->{'post'}{'STRINGS'}{$_[1]}; }, 'advance' => sub { $_[0]->{'hmtx'}{'advance'}[$_[1]]; }, 'xMin' => sub { $_[0]->{'loca'}{'glyphs'}[$_[1]]{'xMin'}; } 'yMin' => sub { $_[0]->{'loca'}{'glyphs'}[$_[1]]{'yMin'}; } 'xMax' => sub { $_[0]->{'loca'}{'glyphs'}[$_[1]]{'xMax'}; } 'yMax' => sub { $_[0]->{'loca'}{'glyphs'}[$_[1]]{'yMax'}; } ); sub _expr_evaluate { my ($tree, $vars, $FUNC) = @_; my ($op, $lhs, $rhs); # return literals up return $tree unless ref $tree; # lookup vars return $vars->{$$tree} if ref $tree eq 'SCALAR'; my $type = $tree->[0]; # handle binary expressions if ($type == BIN_OP) { ($op, $lhs, $rhs) = ($tree->[1], $tree->[2], $tree->[3]); # recurse and resolve subexpressions $lhs = _expr_evaluate($lhs, $vars, $FUNC) if ref($lhs); $rhs = _expr_evaluate($rhs, $vars, $FUNC) if ref($rhs); # do the op $op eq '==' and return $lhs == $rhs; $op eq 'eq' and return $lhs eq $rhs; $op eq '>' and return $lhs > $rhs; $op eq '<' and return $lhs < $rhs; $op eq '!=' and return $lhs != $rhs; $op eq 'ne' and return $lhs ne $rhs; $op eq '>=' and return $lhs >= $rhs; $op eq '<=' and return $lhs <= $rhs; $op eq '+' and return $lhs + $rhs; $op eq '-' and return $lhs - $rhs; $op eq '/' and return $lhs / $rhs; $op eq '*' and return $lhs * $rhs; $op eq '%' and return $lhs % $rhs; if ($op eq 'or' or $op eq '||') { # short circuit or $lhs = _expr_evaluate($lhs, $vars, $FUNC) if ref $lhs; return 1 if $lhs; $rhs = _expr_evaluate($rhs, $vars, $FUNC) if ref $rhs; return 1 if $rhs; return 0; } else { # short circuit and $lhs = _expr_evaluate($lhs, $vars, $FUNC) if ref $lhs; return 0 unless $lhs; $rhs = _expr_evaluate($rhs, $vars, $FUNC) if ref $rhs; return 0 unless $rhs; return 1; } $op eq 'le' and return $lhs le $rhs; $op eq 'ge' and return $lhs ge $rhs; $op eq 'lt' and return $lhs lt $rhs; $op eq 'gt' and return $lhs gt $rhs; confess("Error: unknown op: $op"); } if ($type == FUNCTION_CALL) { croak("Error: found unknown subroutine call : $tree->[1]\n") unless exists($FUNC->{$tree->[1]}); if (defined $tree->[2]) { return $FUNC->{$tree->[1]}->( map { _expr_evaluate($_, $vars, $FUNC) } @{$tree->[2]} ); } else { return $FUNC->{$tree->[1]}->(); } } } sub evaluate { my ($str, $vars, $FUNC) = @_; $tree = $PARSER->expression("($str)"); return _expr_evaluate($tree, $vars, $FUNC); } use Font::TTF::Font; use XML::SAX::Writer; use XML::SAX::ParserFactory; use Getopt::Std; getopts('h'); unless ($ARGV[0] || $opt_h) { pod2usage(1); exit; } if ($opt_h) { pod2usage(-verbose => 2, -noperldoc => 1); exit; } my $f = Font::TTF::Font->open($ARGV[1]); foreach (qw(loca post cmap hmtx)) { $f->{$_}->read; } my $writer = XML::SAX::Writer->new(); my $handler = SAX::Expr->new(Handler => $writer, 'font' => $f); my $p = XML::SAX::ParserFactory->parser(Handler => $handler); $p->parse_uri($ARGV[0]); package SAX::Expr; use base qw(XML::SAX::Base); sub start_element { my ($self, $el) = @_; my ($attrs) = $el->{'Attributes'}; my ($gid) = $self->{'curr_gid'}; my ($f) = $self->{'font'}; if ($el->{'LocalName'} eq 'glyph') { $gid = $f->{'cmap'}->ms_lookup(hex($attrs->{'{}UID'}{'Value'})) if (defined $attrs->{'{}UID'}); $gid = $f->{'post'}{'STRINGS'}{$attrs->{'{}PSName'}{'Value'}} if (defined $attrs->{'{}PSName'}); $gid = $attrs->{'{}GID'}{'Value'} if (defined $attrs->{'{}GID'}); $self->{'curr_gid'} = $gid; my ($vars) = {}; my ($glyph) = $f->{'loca'}{'glyphs'}[$gid]; if ($glyph) { $glyph->read; foreach (qw(xMin yMin xMax yMax)) { $vars->{$_} = $glyph->{$_}; } } $vars->{'adv'} = $f->{'hmtx'}{'advance'}[$gid]; $vars->{'font'} = $f; $self->{'vars'} = $vars; } foreach my $k (qw(x y value)) { next unless (defined $attrs->{"{}$k"}); $attrs->{"{}$k"}{'Value'} =~ s/^=(.*)$/main::evaluate($1, $self->{'vars'}, \%FUNC)/oe; } $self->SUPER::start_element($el); } __END__ =head1 TITLE apexpr - evaluate expressions within an attachment point database =head1 SYNOPSIS apexpr infile.xml infile.ttf