#!/usr/local/bin/perl
# fig2sty Version 0.1
# TeX Layout Generator
# Author: Marcel Rohner
# Date  : 16.5.98
# $Id: fig2sty,v 1.25 2003/01/24 20:00:20 rohner Exp rohner $

# Copyright (C) 1998  Marcel Rohner
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

=head1 NAME

fig2sty - LaTeX Layout Generator

=head1 SYNOPSIS

  fig2sty [-debug] [-baselineskip=..] [-fontsize=..]
          [-reference=(grid|upper|lower)] [-offset=..]
          [-figversion=..] [-nopipe] [-fig2dev=...] input-file

=item C<-debug>

Show base lines in background picture

=item C<-baselineskip>, C<-fontsize>, C<-reference>, C<-offset>

Default values. May be overriden by tags.

=item C<-figversion>

XFig format version which will be fed to C<fig2dev> to generate
background picture

=item C<-nopipe>

If your version of C<fig2dev> does not accept piped input use
this switch

=item C<-fig2dev>

If you have installed several versions of C<fig2dev> you
can specify which version to use

=head1 DESCRIPTION

fig2sty allows you to generate fancy layouts with LaTeX.
The basic idea is to draw layout definitions interactively
with XFig and transform this definition to a LaTeX style
file. You can then use LaTeX to typeset your text into
arbitrarily shaped polygons (frames) within the layout.

You can even add any graphical elements in the layout
definition which will appear in the LaTeX output as
a background picture.

=head1 How to draw the layout definition.

Any closed polygon or box may be a frame. But it must not
have an area fill such that your coloured background
boxes will not interfere with your frames. Tags are
associated to each frame. A tag is simply a text
C<key=value> marked as I<special> and positioned within
the I<bounding box> of the respective frame.
If there are any ambiguities, simply put your frame and its
tags into a compound. 

Several I<key> words are interpreted by C<fig2sty>:

=item C<type>

This tag is compulsory; a frame will not be detected as such
if you don't provide a type tag. You can have several
type tags in your layout, but you may as well have several
frames associated with the same type tag. Text will then
flow between all the frames of common type. The type tag
must neither contain numerals nor any special characters,
just plain alpha characters. If you care
about the ordering of the text flow you can use the
following tag:

=item C<n>

Text will flow between frames of common type in increasing
size of C<n>

=item C<baselineskip>

Text will be typeset in a fixed line grid. Baselineskip 
provides the distance between neighbouring lines. All
frames of a certain type share the same baselineskip. If you
provide several of them, the tag within the frame of lowest
C<n> will be used. If you have rectangular frames of equal widths
only you may set C<baselineskip=any>. Text will then by set in
ordinary horizontal mode. For any other kind of frame or
multiple rectangulars with differing width C<baselineskip=any>
is forbidden.

=item C<reference>

Lines of all frames will be aligned if you assign the
value C<grid> to this tag. Other possibilities are
C<upper>(C<lower>) which will align to the upper(lower) boundary
of your frame. Each frame has its private reference.

=item C<offset>

You can shift your base lines by an arbitrary amount by
providing the C<offset> tag. Each frame has its private
offset.

=item C<fontsize>

Used only as minimal distance from top baseline to
the upper boundary of the frame.

=item C<head>

LaTeX code to be prepended to the text

=item C<tail>

LaTeX code to be appended to the text

Default values to the tags are C<baselineskip=12>,
C<reference=grid>, C<offset=0>, but you can overwrite
these defaults by the command line options. All dimensions
are given in TeX pt.

=head1 How the baselines are chosen

First of all, you can't have lines split horizontally.
Lines will always go from the leftmost intersection of the
base line with the frame to the rightmost one. There is
not really any technical reason for this (except for
laziness, one of the virtues...). If you find this to be
a major restriction, please let me know.

The baselines are chosen such that the distance to the
reference point is a multiple of C<baselineskip>. C<fig2sty>
will ensure that no textual element of size C<fontsize> will
overlap the given frame. This is indeed the only use of the
C<fontsize> tag. C<fontsize=baselineskip> is usually a good
choice except when using small letters at wide vertical spacing.
The choice of the reference point is based on the C<reference>
tag. The value C<grid> means that a global reference point (the
upper-left corner of the layout) will be used. This allows text
in different frames to be aligned. C<reference=upper> means that
text will touch the upper boundary of your frame. The analogous
functionality is provided by the C<lower> value.

=head1 How to use in your LaTeX document.

If you have two frames of type 'C<abstract>' and 'C<text>' within
the layout C<fancylayout> you type:

\documentclass{article}
\usepackage{figtosty}
\usepackage{fancylayout}

	\begin{document}
	...
	\begin{fancylayout}
	\begin{figframe}{abstract}
	blah blah
	\end{figframe}

	\begin{figframe}{text}
	blah blah
	\end{figframe}
	\end{fancylayout}

	...
	\end{document}>

=head1 INSTALLATION

You will need XFig.pm in your Perl Library path
, figtosty.sty in your LaTeX search path and
fig2sty in your binary search path.

=head1 KNOWN BUGS

list environments do not respect frame boundaries

some mysterious bug with splines

doesn't work with \sloppy

=head1 AUTHOR

M. Rohner, rohner@ife.ee.ethz.ch

=head1 CONTRIBUTORS

Thanks to Tobias Oetiker for his helpful comments

=head1 SEE ALSO

XFig.pm

=cut

use XFig;
use strict;
use Getopt::Long;

my $debug = 0;
my $defbaselineskip = 12;
my $deffontsize     = 12;
my $defreference    = 'grid';
my $defoffset       = 0;
my $figversion      = '';
my $nopipe          = 0;
my $fig2dev         = 'fig2dev';

&GetOptions("debug" => \$debug,
            "baselineskip=s" => \$defbaselineskip,
            "fontsize=i"     => \$deffontsize,
            "reference=s"    => \$defreference,
            "offset=i"       => \$defoffset,
            "figversion=s"   => \$figversion,
            "nopipe"         => \$nopipe,
            "fig2dev=s"      => \$fig2dev);

die "Usage: fig2sty layout.fig" if (@ARGV != 1);

my $figname    = $ARGV[0];

$figname =~ m/^(.*)\.(.*?)$/;
my($jobname, $extension) = ($1, $2);
die "Unknown extension $extension" 
  if ($jobname && ($extension !~ /^fig$/));
my $texname    = "$jobname.sty";
my $background = "${jobname}back.ps";

if ($debug) {
  print STDERR "jobname    : $jobname\n";
  print STDERR "texname    : $texname\n";
  print STDERR "background : $background\n";
}

open TEX, ">$texname" || die "Can't open $texname";
my $fig = new XFig;
$fig->parsefile($figname);
die "Can't read file $figname" if ($fig->{'object'} < 0);

# insert marker to define zero point
my $marker = new XFig ('polyline');
$marker->{'thickness'} = 0;
$marker->{'points'} = [ [0,1], [0,0], [1,0] ];
$fig->add($marker);
$fig->{'version'} = $figversion if ($figversion);

# Process FIG input
my @frames     = getFrames($fig);     # set of all frames
my %frameset   = sortFrames(@frames); # frame hash indexed by frame type
# There are basically two types of tags
#  a) tags private to a tag
#  b) tags common to all frames of a certain type
my %commontags = ();
foreach (keys %frameset) {
  $commontags{$_} = {getFrameTags(@{$frameset{$_}})};
  if ( (@{$frameset{$_}} == 1) &&     # check for 'simple' frames
       ($frameset{$_}->[0]->{'subtype'} == 2) &&
       ($commontags{$_}->{'baselineskip'} =~ /^(any)?$/i) ) {
    # simple boxes function like parboxes. 
    # They have no fixed grid
    $commontags{$_}->{'simple'} = 1;
    my $width = $frameset{$_}->[0]->{'maxx'} - $frameset{$_}->[0]->{'minx'};
    my $frame;
    foreach $frame (@{$frameset{$_}}) {
      die "baselineskip=any  requires rectangular frames of equal size in type=$_" 
        unless ( ($frameset{$_}->[0]->{'subtype'} == 2) ||
                 ($width == $frame->{'maxx'} - $frame->{'minx'}) );
      $frame->{'simple'} = 1;
    }
  }
}

# TeX output
my %vinfo = ();    # vertical info
my @hinfo = ();    # horizontal info ([indentation, linewidth] ...)
my $baselineskip;
my $figscale;      # FIG version dependent scaling factor
$figscale = ($fig->{'version'} > '3.1')?0.95:1.0;

# Write style header
print TEX <<"EOTEX";
% This style file has been automatically generated by fig2sty
% Input FIG : $figname (Version $fig->{version})
% fig2sty is copyrighted by (c) M. Rohner
\\newenvironment\{$jobname\}\{
EOTEX

# leftx:  x of leftpost point of frame
# topy :  y of topmost  ...
# vsize:  vertical size of frame
# hinfo:  line size and indentation
my ($leftx, $topy, $vsize, $hinfo, $n);
my %topyset  = ();
my %vsizeset = ();
my %leftxset = ();
# Write paragraph info
foreach (keys %frameset) {
  print TEX "\\newbox\\${_}box\n";

  ($leftx, $topy, $vsize, $hinfo) = computeBaselines($frameset{$_}, $commontags{$_});
  $topyset{$_}  = $topy;
  $vsizeset{$_} = $vsize;
  $leftxset{$_} = $leftx;
  $baselineskip = $commontags{$_}->{'baselineskip'};
 
  print TEX "\\long\\outer\\def\\fts${_}\{% frame command\n";

  if ($commontags{$_}->{'simple'}) {   # simple frame

    print TEX <<"EOTEX"
\\global\\setbox\\${_}box=\\vbox\\bgroup%
%  \\begin\{minipage\}\{$hinfo->[0]pt\}
%   \\parbox\{$hinfo->[0]pt\}\\bgroup
  \\hsize=$hinfo->[0]pt
  \\setlength\{\\textwidth\}\{$hinfo->[0]pt\}
EOTEX

  } else {                             # complex frame -> parshape
    $n = scalar(@$hinfo);

    print TEX <<"EOTEX";
\\global\\setbox\\${_}box=\\vbox\\bgroup%
%\\tolerance 9999\\emergencystretch 3em% sloppy
\\baselineskip=${baselineskip}pt
\\rigidize
\\simulpar
\\parshape=$n
EOTEX

    my $il;
    foreach $il (@$hinfo) {
      print TEX " $il->[0]pt $il->[1]pt";
    }
  }
  print TEX "\n$commontags{$_}->{'head'}" 
    if ($commontags{$_}->{'head'});
  print TEX "\}\n",
            "\\long\\outer\\def\\ftsend${_}\{\\endgraf\n";
  print TEX "$commontags{$_}->{'tail'}\}\n" 
    if ($commontags{$_}->{'tail'});
#  print TEX "\\end\{minipage\}\n"
#  print TEX "\\egroup\n"
#    if ($commontags{$_}->{'simple'});
  print TEX "\}\n";
}
print TEX "\}\n";

# Placement of frames
print TEX "\{\\setbox1=\\vbox\{\\noindent",
          "\\includegraphics\[scale=$figscale\]\{$background\}\}\n",
          "\\dimendef\\w=0 \\w=\\wd1\n",
          "\\dimendef\\h=0 \\h=\\ht1\n",
          "\\splittopskip=0pt\n",
          "\\parindent=0pt\\baselineskip=0pt\\setbox0=\\vbox to0pt\{\n",
          "\\vbox to0pt\{\\unvbox1\\vss\}\n";

foreach (keys %frameset) {
  $topy = $topyset{$_};
  $vsize = $vsizeset{$_};
  $leftx = $leftxset{$_};
  while (@$topy) {
    print TEX '\vbox to0pt{\kern', shift @$topy, 'pt',
              '\hskip', shift @$leftx, 'pt',
              "\\vsplit\\${_}box to", shift @$vsize, 'pt\vss}', "\n";
  }
}
# Check wether text fits into frames
print TEX '}\unvbox0', "\n";  
foreach (keys %frameset) {
  print TEX "\\ifvoid\\${_}box \\else",
            "\\message\{fig2sty: Frame '${_}' overflow\}\\fi\n";
}
print TEX "\\vskip\\h\}\n";
close TEX;

# Generate output picture
if ($nopipe) {
  # some fig2dev versions don't accept pipes
  my $tmpfile;
  $tmpfile = "/tmp/fig2sty$$.tmp";
  $fig->writefile(">$tmpfile");
  `$fig2dev -Lps -e -p dummy $tmpfile > $background`;
  unlink $tmpfile;
} else {
  $fig->writefile("|$fig2dev -Lps -e -p dummy > $background");
  die "fig2dev failed" if $?;
};

# ------------------------------------------------------------------------------
# Subroutines
#
# getFrames
# Traverse Fig Tree
# Tags are removed
# line thickness of the frames is set to 0
# Input: fig tree
# Output: @frames = (poly, ...)
sub getFrames {
  my $fig = shift;
  my @frames = ();
  my @polylist = ();
  my @taglist  = ();

  # Find frames and tags within compound
  # Note: frames and tags must be within the same compound
  my $element;
  for $element ($fig->eachPrimitive()) {
    # element is poly
    do {
      # find bounding box
      my($minx, $maxx, $miny, $maxy) = (1e39, -1e39, 1e39, -1e39);
      foreach (@{$element->{points}}) {
        $minx = $_->[0] if ($_->[0] < $minx);
        $maxx = $_->[0] if ($_->[0] > $maxx);
        $miny = $_->[1] if ($_->[1] < $miny);
        $maxy = $_->[1] if ($_->[1] > $maxy);
      }
      $element->{'minx'} = $minx;
      $element->{'maxx'} = $maxx;
      $element->{'miny'} = $miny;
      $element->{'maxy'} = $maxy;
      push @polylist, $element;
    } if ($element->isPolyline() && 
          $element->isClosed() &&
          ($element->{'areafill'} == -1));

    # element is tag
    do {
      my($key, $value) = split '=', $element->{'text'};
      $element->{'key'} = $key;
      $element->{'value'} = $value;
      push @taglist, $element;
#        if ($key =~ /(baselineskip|offset|reference|n)/);
    } if ($element->isText() &&
         ($element->{'fontflags'} & 2));
  }

  # assign tags to frames
  # Note: a tag is assigned to one frame only
  #       if two frames overlap, all tags will be assigned
  #          to one of them only
  my $tag;
  TAG: foreach $tag (@taglist) {
    my ($key, $value) = @$tag{'key', 'value'};
    my $poly;
    foreach $poly (@polylist) {
      do {
        if ($debug) {
          print STDERR "assigned tag $key=$value at ",
                       "($tag->{'x'}, $tag->{'y'})\n";
        }
        $poly->{$key} = $value;
        $poly->{'isFrame'} = 1 if ($key =~ /type/);
        $tag->delete();             # delete tag from layout
        $poly->{'thickness'} = 0.0; # make frame invisible
        next TAG;
      } if ($tag->{'x'} > $poly->{'minx'} &&
            $tag->{'x'} < $poly->{'maxx'} &&
            $tag->{'y'} > $poly->{'miny'} &&
            $tag->{'y'} < $poly->{'maxy'});
    }
  }

  @frames = grep { $_->{'isFrame'} } @polylist;

  # recursion into compounds
  for $element ($fig->eachCompound()) {
    push @frames, getFrames($element);
  }    

  return @frames;
}

# sortFrames
# Sort by frametype and increasing n
sub sortFrames {
  my @frames = @_;
  my %frameset = ();
  
  # split into frame types
  foreach (@frames) {
    push @{$frameset{$_->{'type'}}}, $_;
  }

  # sort in increasing n
  foreach (keys %frameset) {
    @{$frameset{$_}} = sort { $a->{'n'} <=> $b->{'n'} }
       @{$frameset{$_}};
  }

  return %frameset;
}

# Find common tags
sub getFrameTags {
  my @frames = @_;
  my %tags = ();

  # default values
  $tags{'baselineskip'} = $defbaselineskip;
  $tags{'fontsize'}     = $deffontsize || $defbaselineskip;

  # user values
  my ($frame, $tagname);
  foreach $frame (reverse @frames) {
    foreach $tagname ('baselineskip', 'fontsize', 'head', 'tail') {
      $tags{$tagname} = $frame->{$tagname}
        if ($frame->{$tagname}); 
    }
  }

  return %tags;
}

sub computeBaselines {
  my ($refframes, $reftags) = @_;
  my $baselineskip = $reftags->{'baselineskip'};
  my $fontsize     = $reftags->{'fontsize'};

  my(@hinfo, @leftx, @topy, @vsize);
  my $frame;

  if ($reftags->{'simple'}) { # simple box
    foreach $frame (@$refframes) {
      my @x = sort { $a->[0] <=> $b->[0] } @{$frame->{'points'}};
      my @y = sort { $a->[1] <=> $b->[1] } @{$frame->{'points'}};
      my $minx = $x[0]->[0];
      my $miny = $y[0]->[1];
      my $height = $y[3]->[1] - $y[0]->[1];
      my $width = $x[3]->[0] - $x[0]->[0];
      push @leftx, fig2pt($minx);
      push @topy,  fig2pt($miny);
      push @vsize, fig2pt($height);
      push @hinfo, fig2pt($width);
    }
    return (\@leftx, \@topy, \@vsize, \@hinfo);
  }

  my($refpoint, $nolines, $x1, $x2, $firstline, $liney, $offset, $reference);

  foreach $frame (@$refframes) {
    $offset       = $frame->{'offset'} || $reftags->{'offset'};
    $reference    = $frame->{'reference'} || $reftags->{'reference'};
    $refpoint = $frame->{'miny'} if ($reference =~ /^u/i);
    $refpoint = $frame->{'maxy'} if ($reference =~ /^l/i);
    $refpoint += pt2fig($offset);
    if ($refpoint < $frame->{'miny'} ) { # round up
      $firstline = int (fig2pt($frame->{'miny'} - $refpoint) / $baselineskip + 1)
                    * $baselineskip;
    } else {
      $firstline = - int (fig2pt($refpoint - $frame->{'miny'}) / $baselineskip )
                    * $baselineskip;
    }
    $firstline += fig2pt($refpoint);
    $firstline += $baselineskip
       if ($firstline < (fig2pt($frame->{'miny'}) + $fontsize));
    $liney = pt2fig($firstline);

    $nolines = 0;
    while ( ($x1, $x2) = intersect($liney, @{$frame->{'points'}})) {
      $nolines++;
      push @hinfo, [fig2pt($x1), fig2pt($x2-$x1)];
      if ($debug) {
        my $debugline;
        $debugline = new XFig ('Polyline');
        $debugline->{'subtype'} = 1;  # line
        $debugline->{'points'} = [ [$x1, int($liney)], [$x2, int($liney)] ];
        $fig->add($debugline);
      }
      $liney += pt2fig($baselineskip);
    }
    push @topy, $firstline - $fontsize;
    push @leftx, fig2pt($x1);
    push @vsize, $nolines*$baselineskip;
  } 

  return (\@leftx, \@topy, \@vsize, \@hinfo);
}

sub intersect {
  my ($firstline, @points) = @_;
  my $minx = 1e39;
  my $maxx = -1e39;
  my $isectfound = 0;
 
  my($p1, $p2, $x);
  $p2 = shift @points; 
  while (@points) {
    $p1 = $p2;
    $p2 = shift @points;
    next if ( ($firstline - $p1->[1])*($firstline - $p2->[1]) > 0 ||
              ($p2->[1] == $p1->[1]));
    $x = $p1->[0] + ($p2->[0] - $p1->[0]) / ($p2->[1] - $p1->[1]) *
           ($firstline - $p1->[1]);
    $minx = $x if ($x < $minx);
    $maxx = $x if ($x > $maxx);
    $isectfound = 1;
  } 
  if ($isectfound) {
    return (int($minx), int($maxx));
  } else {
    return ();
  }
}

# ------------------------------------------------------------
# Conversion pt <-> fig
sub pt2fig {
  my $pts = shift;
  return $pts * $fig->{'resolution'} / 72.27;
}

sub fig2pt {
  my $figs = shift;
  return $figs * 72.27 / $fig->{'resolution'};
}
