% File:          latex.sl                            -*- mode: SLang -*-
%
% Copyright (c)
%       2007          Jörg Sommer <[email protected]>
%       $Id: latex_pst.sl 199 2007-08-23 23:28:52Z joerg $
%
%       -*- This file is part of Jörg's LaTeX Mode (JLM) -*-
%
% Description:   This file contains all functions related to PSTricks.
%
% License: 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.

%%%%%%%%%%
%
% A point in PSTricks
%
typedef struct {
   x, y, mark
} Pst_Point_Type;

static define pst_point_as_string(pnt)
{
   return "(" + string(pnt.x) + "," + string(pnt.y) + ")";
}

__add_string(Pst_Point_Type, &pst_point_as_string());

static define pst_new_point() % (x, y[, mark])
{
   variable pnt = @Pst_Point_Type;
   if (_NARGS > 2)
     pnt.mark = ();

   pnt.y = ();
   pnt.x = ();
   return pnt;
}

static define pst_add_points(a, b)
{
   return pst_new_point(a.x + b.x, a.y + b.y);
}
__add_binary("+", Pst_Point_Type, &pst_add_points(), Pst_Point_Type,
            Pst_Point_Type);

static define pst_point_min(pnt1, pnt2)
{
   return pst_new_point( min([pnt1.x, pnt2.x]), min([pnt1.y, pnt2.y]) );
}

static define pst_point_max(pnt1, pnt2)
{
   return pst_new_point( max([pnt1.x, pnt2.x]), max([pnt1.y, pnt2.y]) );
}

static define pst_looking_at_point()
{
   % return re_looking_at("[ \t\n]*(');
   return looking_at_char('(');
}

static define pst_skip_point()
{
   return andelse {fsearch_char(')')} {right(1)};
}

static define pst_update_point(pnt, new_pnt)
{
   if (pnt.mark == NULL)
     throw UsageError, "pnt is not a real point";

   push_spot();
   EXIT_BLOCK
   {
       pop_spot();
   }
   goto_user_mark(pnt.mark);
   % insert before delete to make a spot after the deleted string is left
   % there
   insert( pst_point_as_string(new_pnt) );
   push_mark();
   () = pst_skip_point();
   del_region();
   pnt.x = new_pnt.x;
   pnt.y = new_pnt.y;
}

static define pst_read_point()
{
   !if ( pst_looking_at_point() )
     throw UsageError, "No at a point";

   variable mark = create_user_mark();

   () = right(1);
   push_mark();
   !if ( pst_skip_point() )
   {
       pop_mark(0);
       goto_user_mark(mark);
       throw DataError, "This doesn't look like a PSTricks point ($what_line,"$ +
         string(what_column()) + ")";
   }

   variable x, y;
   if ( sscanf(str_delete_chars(bufsubstr(), "\s"R), "%g,%g", &x, &y) != 2 )
   {
       goto_user_mark(mark);
       throw DataError, "This doesn't look like a PSTricks point ($what_line,"$ +
         string(what_column()) + ")";
   }

   return pst_new_point(x, y, mark);
}

private define pst_extract_points_from_region()
{
   check_region(1);
   EXIT_BLOCK
   {
       pop_spot();
   }
   variable end_mark = create_user_mark();
   pop_mark(1);

   variable point_list = list_new();
   while (create_user_mark() < end_mark)
   {
       skip_chars("^\\{"R);
       if ( is_commented() )
       {
           eol();
           continue;
       }
       if ( looking_at("\\{") or looking_at("\\\\") )
       {
           () = right(2);
           continue;
       }
       if ( looking_at_char('{') )
       {
           fsearch_matching_brace();
           continue;
       }

       () = right(1);
       push_mark();
       skip_chars(TeX_Command_Chars);
       !if ( is_list_element("rput,psframe,psline,pscircle,psbezier,psdot,psdots,psaxes",
                             bufsubstr(), ',') )
         continue;
       (,) = cmd_parse_args(1,0);
       !if ( pst_looking_at_point() )
         throw DataError, "Malformed pstricks command in line $what_line"$;
       do
       {
           list_append(point_list, pst_read_point());
       }
       while ( pst_looking_at_point() );
   }

   return point_list;
}

%%%%%%%%%%
%
% PSTricks
%
static variable PST_Origin = pst_new_point(0,0);

static define pst_enlarge_pic(point)
{
   push_spot();
   EXIT_BLOCK
   {
       pop_spot();
   }

   boenv();
   !if ( looking_at("\\begin{pspicture}") )
     throw UsageError, "This is not a pspicture environment";

   () = right(17);
   if ( pst_looking_at_point() )
   {
       variable old_point = pst_read_point(), tmp_point;
       if ( pst_looking_at_point() )
       {
           tmp_point = pst_point_min(old_point, point);
           if (tmp_point.x < old_point.x or tmp_point.y < old_point.y)
           {
               if (tmp_point.x == PST_Origin.x and tmp_point.y == PST_Origin.y)
               {
                   push_spot();
                   goto_user_mark(old_point.mark);
                   push_mark();
                   pst_skip_point();
                   del_region();
                   pop_spot();
               }
               else
                 pst_update_point(old_point, tmp_point);
           }

           old_point = pst_read_point();
       }
       else if (point.x < PST_Origin.x or point.y < PST_Origin.y)
       {
           push_spot();
           goto_user_mark(old_point.mark);
           insert( pst_point_as_string(pst_point_min(PST_Origin, point)) );
           pop_spot();
       }

       tmp_point = pst_point_max(old_point, point);
       if (old_point.x < tmp_point.x or old_point.y < tmp_point.y)
       {
           if (tmp_point.x == PST_Origin.x and tmp_point.y == PST_Origin.y)
           {
               push_spot();
               goto_user_mark(old_point.mark);
               push_mark();
               pst_skip_point();
               del_region();
               pop_spot();
           }
           else
             pst_update_point(old_point, tmp_point);
       }
   }
   else
   {
       if (point.x < PST_Origin.x or point.y < PST_Origin.y)
         insert( pst_point_as_string(pst_point_min(point, PST_Origin)) +
                 pst_point_as_string(pst_point_max(point, PST_Origin)) );
       else if (point.x > PST_Origin.x or point.y > PST_Origin.y)
         insert( pst_point_as_string(point) );
   }
}

static define pst_update_pic_size()
{
   push_spot();
   EXIT_BLOCK
   {
       pop_spot();
   }

   boenv();
   !if ( looking_at("\\begin{pspicture}") )
     throw UsageError, "This is not a pspicture environment";
   () = right(17);
   while ( pst_looking_at_point() )
     () = pst_skip_point();

   push_mark();
   eoenv();

   variable min = PST_Origin, max = PST_Origin;
   foreach ( pst_extract_points_from_region() )
   {
       dup;
       min = pst_point_min((), min);
       max = pst_point_max((), max);
   }

   pst_enlarge_pic(min);
   pst_enlarge_pic(max);
}

static define pst_move_points()
{
   variable offset;
   try
     offset = read_mini("Offset to move points; use X for (X,X) or (X,Y)",
                        "", "");
   catch UserBreakError:
     return;

   variable x, y;
   if ( andelse {sscanf(offset, "%g", &x) != 1}
        {sscanf(offset, "(%g,%g)", &x, &y) != 2})
     throw UsageError, "Invalid input for offset";

   !if ( __is_initialized(&y) )
     y = x;

   offset = pst_new_point(x, y);
   variable max = PST_Origin, min = PST_Origin, p;
   foreach p ( pst_extract_points_from_region() )
   {
       variable new_p = p + offset;
       min = pst_point_min(min, new_p);
       max = pst_point_max(max, new_p);
       pst_update_point(p, new_p);
   }
   pst_enlarge_pic(min);
   pst_enlarge_pic(max);
}