# This file is part of reLyX # Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu # You are free to use and modify this code under the terms of # the GNU General Public Licence version 2 or later. package RelyxFigure; # This is a package to read LaTeX figures and print out LyX figures # We declare here the sub-packages found in this package. # This allows the parser to understand "indirect object" form of subroutines #{ #package RelyxTable::Table; #package RelyxTable::Column; #package RelyxTable::Row; #} use strict; # Variables used by other packages use vars qw($EpsfYsize $EpsfXsize %HW_types); # Debugging on? my $debug_on; # This means "display in monochrome" and "do translations", but who cares? # It's just here because this is the default that LyX outputs. my $Default_Flags = 9; # Names for width_type & height_type fields. %HW_types = ( "def" => 0, "cm" => 1, "in" => 2, "per_page" => 3, # percent of the page "per_col" => 4, # percent of a column (illegal for height_type) ); # Parse \epsfxsize or \epsfysize command # Return 0 if we can't convert the length (in which case we have to type # the \epsf[xy]size command in tex mode). sub parse_epsfsize { # Command & length have already been "stringified" (they're not tokens) my ($command, $length) = (shift,shift); if ($command eq '\\epsfxsize') { $EpsfXsize = $length; my @dummy = &convert_length($EpsfXsize) || return 0; } elsif ($command eq '\\epsfysize') { $EpsfYsize = $length; my @dummy = &convert_length($EpsfXsize) || return 0; } return 1; } # end sub RelyxFIgure::Figure::parse_epsfig sub convert_length { # test if it's a valid LyX width/height. # (But assume a person won't try to set width to \textheight, # and that they won't have negative or otherwise weird lengths) # Then convert to width & width_type (or height/height_type) # Return empty list on error my $size = shift; # A length can be (optional plus followed by) (num)(unit) where # num is a float number (possibly with European command) and unit # is a size unit, either in,cm,pt etc. or \textwidth etc. my %unit_convert = ( # 1 inch = 25.4 mm "mm" => 25.4, "pt" => 72.27, "bp" => 72, "pc" => 72.27/12, "dd" => 72.27*1157/1238, "cc" => 72.27*1157/(1238*12), ); my ($number, $type); if ($size =~ /^\+?([\d.,]+)(cm|in)$/) { ($number, $type) = ($1, $2); $number =~ s/,/./; # Allow european numbers! # print "length is $number '$type'"; } elsif ($size =~ /^\+?([\d.,]+)(mm|pt|bp|pc|dd|cc)$/) { ($number, $type) = ($1, $2); $number =~ s/,/./; $number = $number / $unit_convert{$type}; $type = "in"; } elsif ($size =~ /^\+?([\d.,]*)\s*\\text(height|width)$/) { $number = $1 || 1; $number =~ s/,/./; $number *= 100; $type = "per_page"; } elsif ($size =~ /^\+?([\d.,]*)\s*\\columnwidth$/) { $number = $1 || 1; $number =~ s/,/./; $number *= 100; $type = "per_col"; } else { print "\ncannot translate length '$size' in Figure; ", "copying in TeX mode\n" if $debug_on; } if ($number) { return ($number, $type); } else { return (); } } # end sub convert_length sub parse_keyval { # Parse a string containing comma-separated "key=value" pairs # Compare the keys with a list of known keys. # If we know all keys, return a hash containing keys/values # Else return undef. my ($string, @known_keys) = @_; my @fields = split(/\s*,\s*/,$string); my %fighash; foreach (@fields) { # split "key=val" into fighash{key}=val my ($key,$val) = split(/\s*=\s*/,$_); $val = "" unless defined $val; # e.g., 'clip=' $fighash{$key} = $val; unless (grep /^$key$/, @known_keys) { print "\nUntranslatable key '$key' to figure;", " copying in TeX mode\n" if $debug_on; return undef; } } return \%fighash; } # end sub parse_keyval { package RelyxFigure::Figure; # reLyX figure class # Fields: # file - file name # width - width # height - height # width_type - is width in cm, % of pagewidth, etc.? # height_type - is height in cm, % of pagewidth, etc.? # angle - rotate fig through angle # flags - various flags for LyX display. Not important sub new { my $class = shift; my $thisfig; $thisfig->{'file'} = ""; $thisfig->{'width'} = 0; $thisfig->{'height'} = 0; $thisfig->{'width_type'} = "def"; $thisfig->{'height_type'} = "def"; $thisfig->{'angle'} = 0; $thisfig->{'flags'} = $Default_Flags; # This seems like a convenient place to declare this... $debug_on= (defined($main::opt_d) && $main::opt_d); bless $thisfig, $class; } # end sub RelyxFigure::Figure::new sub parse_pscommand { # this sub is given the various arguments to a command & adds that # information to the figure object. # Return 0 if it can't read the command, or if LyX can't handle it. Else 1. # # command is the name of the postscript command # optargs are optional arguments (TT:Tokens). For many of the commands, # only one optarg is allowed. And of course, they may be empty tokens. # reqarg is usually the filename (for (e)psfig, it's more) # # Currently, LyX can't handle bounding box, so we always return 0 if one # is given. my ($self, $command, $optarg1, $optarg2, $reqarg) = @_; my (@known_keys, $filename, $keyval_string, %fighash); for ($command) { if (/^\\epsf(file|box)?$/) { # syntax: \epsffile[bounding box]{filename.eps} # epsffile is an older version of epsfbox return 0 if $optarg1->print; # bounding box was given. Panic! $filename = &recursive_print($reqarg); # fighash key shouldn't exist if no size was given $fighash{'height'} = $RelyxFigure::EpsfYsize if $RelyxFigure::EpsfYsize; $fighash{'width'} = $RelyxFigure::EpsfXsize if $RelyxFigure::EpsfXsize; # Once you use \epsf[xy]size, they get reset $RelyxFigure::EpsfXsize = $RelyxFigure::EpsfYsize = ""; $keyval_string = ""; # no key/value pairs for this command } elsif (/^\\e?psfig$/) { # "silent" key doesn't do anything @known_keys = qw(file figure rotate angle height width silent); $keyval_string = &recursive_print($reqarg); my $fighashref = &RelyxFigure::parse_keyval($keyval_string, @known_keys); return 0 unless defined $fighashref; # found unknown key... %fighash = %$fighashref; $filename = $fighash{'file'} || $fighash{'figure'} || warn "no filename found in figure argument '$keyval_string'"; } elsif (/^\\includegraphics$/) { # 0 optargs can be either graphics or graphicx. Doesn't # matter, matter, it's just the filename. # 1 optarg can either be graphicx (if arg contains '=') or # graphics (optarg is upper right; lower left is 0,0). # 2 optargs is graphics with bounding box. # Optional arguments are always returned as single tokens, # not groups. So we can use the print method instead of # recursive_print. $keyval_string = $optarg1->print; if ($keyval_string) { if ($keyval_string =~ /=/) { # graphicx form $keyval_string =~ s/\[(.*)\]/$1/; # remove '[' and ']' @known_keys = qw(rotate angle height width); my $fighashref = &RelyxFigure::parse_keyval( $keyval_string, @known_keys); return 0 unless defined $fighashref; # found unknown key %fighash = %$fighashref; } else { # graphics form with bounding box print "\nLyX cannot support bounding box; ", "copying Figure in TeX mode\n" if $debug_on; return 0; } } $filename = &recursive_print($reqarg); } } # end switch on command name # Now set fields in the Figure object $self->{'file'} = $filename; $self->{'angle'} = $fighash{'rotate'} if exists $fighash{'rotate'}; $self->{'angle'} = $fighash{'angle'} if exists $fighash{'angle'}; if (exists $fighash{'height'}) { my @heights = &RelyxFigure::convert_length($fighash{'height'}); return 0 unless @heights; # unconvertable length! ($self->{'height'},$self->{'height_type'}) = @heights; } if (exists $fighash{'width'}) { my @widths = &RelyxFigure::convert_length($fighash{'width'}); return 0 unless @widths; # unconvertable length! ($self->{'width'},$self->{'width_type'}) = @widths; } return 1; # if we got here, we parsed correctly and LyX can handle it. } # end sub RelyxFigure::Figure::parse_pscommand sub recursive_print { # if arg is a group, print its contents (i.e., ignore '{' and '}') # otherwise, print arg my $tok = shift; my $filename = ""; my $type = ref($tok); $type =~ s/Text::TeX::// or warn "weird group"; if ($type eq 'Group') { foreach ($tok->contents) {$filename .= &recursive_print($_)} } else { $filename .= $tok->exact_print } return $filename; } sub print_info { # LyX figure command -- return what to print; don't actually print it my $self = shift; my $to_print = "\n\\begin_inset Figure\n"; # (LyX fig. command has eps size here. But we don't know that, so # we dont print it out.) $to_print .= "file $self->{'file'}\n"; my ($size, $type) = ("",""); ($size, $type) = ($self->{'width'}, $RelyxFigure::HW_types{$self->{'width_type'}}); $to_print .= "width $type $size\n" if $size; ($size, $type) = ("",""); ($size, $type) = ($self->{'height'}, $RelyxFigure::HW_types{$self->{'height_type'}}); $to_print .= "height $type $size\n" if $size; $to_print .= "angle $self->{'angle'}\n" if $self->{'angle'}; $to_print .= "flags $self->{'flags'}\n"; $to_print .= "\n\\end_inset \n\n"; } # end sub RelyxFigure::Figure::print } # end of package RelyxFigure::Figure 1; # return true to calling package