% File:          latex.sl                            -*- mode: SLang -*-
%
% Copyright (c)
%       2007          Jörg Sommer <joerg@alea.gnuu.de>
%       $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);
}
