/* Copyright (C) 2001-2026 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied,
   modified or distributed except as expressly authorized under the terms
   of the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
   CA 94129, USA, for further information.
*/


/* pgvector.c */
/* HP-GL/2 vector commands */

#include "stdio_.h"             /* for gdebug.h */
#include "gdebug.h"
#include "pcparse.h"
#include "pgmand.h"
#include "pggeom.h"
#include "pgdraw.h"
#include "pgmisc.h"
#include "gspath.h"
#include "gscoord.h"
#include "math_.h"

/* ------ Internal procedures ------ */

/* Draw an arc (AA, AR). */
static int
hpgl_arc(hpgl_args_t * pargs, hpgl_state_t * pgls, bool relative)
{
    hpgl_real_t x_center, y_center, sweep, x_current, y_current, chord_angle =
        5;
    hpgl_real_t radius, start_angle;

    if (!hpgl_arg_units(pgls->memory, pargs, &x_center) ||
        !hpgl_arg_units(pgls->memory, pargs, &y_center) ||
        !hpgl_arg_c_real(pgls->memory, pargs, &sweep)
        )
        return e_Range;

    hpgl_arg_c_real(pgls->memory, pargs, &chord_angle);

    if (current_units_out_of_range(x_center) ||
        current_units_out_of_range(y_center))
        return 0;

    x_current = pgls->g.pos.x;
    y_current = pgls->g.pos.y;

    if (relative) {
        x_center += x_current;
        y_center += y_current;
    }

    radius = hpgl_compute_distance(x_current, y_current, x_center, y_center);

    start_angle = radians_to_degrees *
        hpgl_compute_angle(x_current - x_center, y_current - y_center);

    hpgl_call(hpgl_add_arc_to_path(pgls, x_center, y_center,
                                   radius, start_angle, sweep,
                                   chord_angle,
                                   false, pgls->g.move_or_draw, true));

    hpgl_call(hpgl_update_carriage_return_pos(pgls));
    return 0;
}

/* Draw a 3-point arc (AT, RT). */
static int
hpgl_arc_3_point(hpgl_args_t * pargs, hpgl_state_t * pgls, bool relative)
{
    hpgl_real_t x_start = pgls->g.pos.x, y_start = pgls->g.pos.y;
    hpgl_real_t x_inter, y_inter, x_end, y_end;
    hpgl_real_t chord_angle = 5;

    if (!hpgl_arg_units(pgls->memory, pargs, &x_inter) ||
        !hpgl_arg_units(pgls->memory, pargs, &y_inter) ||
        !hpgl_arg_units(pgls->memory, pargs, &x_end) ||
        !hpgl_arg_units(pgls->memory, pargs, &y_end)
        )
        return e_Range;

    hpgl_arg_c_real(pgls->memory, pargs, &chord_angle);

    if (relative) {
        x_inter += x_start;
        y_inter += y_start;
        x_end += x_start;
        y_end += y_start;
    }

    hpgl_call(hpgl_add_arc_3point_to_path(pgls,
                                          x_start, y_start,
                                          x_inter, y_inter,
                                          x_end, y_end, chord_angle,
                                          pgls->g.move_or_draw));
    hpgl_call(hpgl_update_carriage_return_pos(pgls));
    return 0;
}

/* Draw a Bezier (BR, BZ). */
static int
hpgl_bezier(hpgl_args_t * pargs, hpgl_state_t * pgls, bool relative)
{
    hpgl_real_t x_start, y_start;

    /*
     * Since these commands take an arbitrary number of arguments,
     * we reset the argument bookkeeping after each group.
     */

    for (;;) {
        hpgl_real_t coords[6];
        int i;

        for (i = 0; i < 6 && hpgl_arg_units(pgls->memory, pargs, &coords[i]);
             ++i);
        switch (i) {
            case 0:            /* done */
                hpgl_call(hpgl_update_carriage_return_pos(pgls));
                /* draw the path */
                if (!pgls->g.polygon_mode) {
                    /* apparently only round and beveled joins are
                       allowed 5 is bevel and 4 is round */
                    int save_join = pgls->g.line.join;

                    if (pgls->g.line.join != 1 && pgls->g.line.join != 4)
                        pgls->g.line.join = 1;  /* bevel */
                    hpgl_call(hpgl_draw_current_path(pgls, hpgl_rm_vector));
                    pgls->g.line.join = save_join;
                }
                return 0;
            case 6:
                break;
            default:
                hpgl_call(hpgl_update_carriage_return_pos(pgls));
                return e_Range;
        }

        x_start = pgls->g.pos.x;
        y_start = pgls->g.pos.y;

        if (relative)
            hpgl_call(hpgl_add_bezier_to_path(pgls, x_start, y_start,
                                              x_start + coords[0],
                                              y_start + coords[1],
                                              x_start + coords[2],
                                              y_start + coords[3],
                                              x_start + coords[4],
                                              y_start + coords[5],
                                              pgls->g.move_or_draw));
        else
            hpgl_call(hpgl_add_bezier_to_path(pgls, x_start, y_start,
                                              coords[0], coords[1],
                                              coords[2], coords[3],
                                              coords[4], coords[5],
                                              pgls->g.move_or_draw));
        /* Prepare for the next set of points. */
        hpgl_args_init(pargs);
    }
}

/* Plot points, symbols, or lines (PA, PD, PR, PU). */
static int
hpgl_plot(hpgl_args_t * pargs, hpgl_state_t * pgls,
          hpgl_plot_function_t func, bool is_PA)
{
    /*
     * Since these commands take an arbitrary number of arguments,
     * we reset the argument bookkeeping after each group.
     */
    /*    bool first_loop = true; */
    hpgl_real_t x, y;

    if (hpgl_plot_is_move(func) && !pgls->g.polygon_mode) {
        hpgl_call(hpgl_close_path(pgls));
    }
    while (hpgl_arg_units(pgls->memory, pargs, &x) &&
           hpgl_arg_units(pgls->memory, pargs, &y)) {

        if (current_units_out_of_range(x) || current_units_out_of_range(y))
            return e_Range;

        pargs->phase = 1;       /* we have arguments */

        hpgl_call(hpgl_add_point_to_path(pgls, x, y, func, true));
        if (hpgl_plot_is_draw(func))
            pgls->g.have_drawn_in_path = true;

        /* Prepare for the next set of points. */
        if (pgls->g.symbol_mode != 0)
            hpgl_call(hpgl_print_symbol_mode_char(pgls));
        hpgl_args_init(pargs);
    }

    /* Check for no argument, no polygon, absolute PD will cause a
       point to be added to the path.  A PA command without arguments
       will not add a point.  NB this probably needs more testing. */

    if (!pargs->phase && hpgl_plot_is_absolute(func) && !pgls->g.polygon_mode
        && !is_PA) {
        gs_point cur_point;

        hpgl_call(hpgl_get_current_position(pgls, &cur_point));
        hpgl_call(hpgl_add_point_to_path(pgls, cur_point.x,
                                         cur_point.y, func, true));
    }
    if (pgls->g.symbol_mode != 0)
        hpgl_call(hpgl_print_symbol_mode_char(pgls));

    /* don't update if no arguments */
    if (pargs->phase)
        hpgl_call(hpgl_update_carriage_return_pos(pgls));
    return 0;
}

/* ------ Commands ------ */
static int
hpgl_draw_arc(hpgl_state_t * pgls)
{
    if (!pgls->g.polygon_mode)
        hpgl_call(hpgl_draw_current_path(pgls, hpgl_rm_vector));
    return 0;
}

/* AA xcenter,ycenter,sweep[,chord]; */
int
hpgl_AA(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    hpgl_call(hpgl_arc(pargs, pgls, false));
    hpgl_call(hpgl_draw_arc(pgls));
    return 0;
}

/* AR xcenter,ycenter,sweep[,chord]; */
int
hpgl_AR(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    hpgl_call(hpgl_arc(pargs, pgls, true));
    hpgl_call(hpgl_draw_arc(pgls));
    return 0;
}

/* AT xinter,yinter,xend,yend[,chord]; */
int
hpgl_AT(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    hpgl_call(hpgl_arc_3_point(pargs, pgls, false));
    hpgl_call(hpgl_draw_arc(pgls));
    return 0;
}

/* BR x1,y1,x2,y2,x3,y3...; */
int
hpgl_BR(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    return hpgl_bezier(pargs, pgls, true);
}

/* BZ x1,y1,x2,y2,x3,y3...; */
int
hpgl_BZ(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    return hpgl_bezier(pargs, pgls, false);
}

/* CI radius[,chord]; */
int
hpgl_CI(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    hpgl_real_t radius, chord = 5;
    bool reset_ctm = true;
    gs_point pos;

    if (!hpgl_arg_units(pgls->memory, pargs, &radius))
        return e_Range;

    /* center; closing subpolygon can move center */
    pos = pgls->g.pos;

    if (!pgls->g.polygon_mode)
        hpgl_call(hpgl_draw_current_path(pgls, hpgl_rm_vector));

    hpgl_arg_c_real(pgls->memory, pargs, &chord);

    /* draw the arc/circle */
    hpgl_call(hpgl_add_arc_to_path(pgls, pos.x, pos.y,
                                   radius, 0.0, 360.0, chord, true,
                                   hpgl_plot_draw_absolute, reset_ctm));
    if (!pgls->g.polygon_mode)
        hpgl_call(hpgl_draw_arc(pgls));

    hpgl_call(hpgl_close_current_path(pgls));
    pgls->g.have_drawn_in_path = false; /* prevent dot draw on close */
    hpgl_call(hpgl_set_current_position(pgls, &pos));

    if (!pgls->g.polygon_mode)
        hpgl_call(hpgl_clear_current_path(pgls));

    return 0;
}

/* PA x,y...; */
int
hpgl_PA(hpgl_args_t * pargs, hpgl_state_t * pgls)
{

    if (pgls->g.relative_coords != hpgl_plot_absolute) {
        pgls->g.relative_coords = hpgl_plot_absolute;
        if (!pgls->g.polygon_mode) {
            hpgl_call(hpgl_draw_current_path(pgls, hpgl_rm_vector));
            hpgl_call(hpgl_clear_current_path(pgls));
        }
    }
    return hpgl_plot(pargs, pgls,
                     pgls->g.move_or_draw | hpgl_plot_absolute,
                     true /* is PA command */ );
}

/* PD (d)x,(d)y...; */
int
hpgl_PD(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    pgls->g.move_or_draw = hpgl_plot_draw;
    return hpgl_plot(pargs, pgls,
                     hpgl_plot_draw | pgls->g.relative_coords,
                     false /* is PA command */ );
}

/* PE (flag|value|coord)*; */
/*
 * We record the state of the command in the 'phase' as follows:
 */
enum
{
    pe_pen_up = 1,              /* next coordinate are pen-up move */
    pe_absolute = 2,            /* next coordinates are absolute */
    pe_7bit = 4,                /* use 7-bit encoding */
    pe_entered = 8              /* we have entered PE once */
};

/* convert pe fixed to float accounting for fractional bits */
static inline double
pe_fixed2float(int32 x, int32 fbits)
{
    return ((double) x * (1.0 / pow(2, fbits)));
}

static int pe_args(const gs_memory_t * mem, hpgl_args_t *,
                                  int);

static inline void
pe_clear_args(hpgl_args_t * pargs)
{
    pargs->pe_shift[0] = pargs->pe_shift[1] = 0;
    pargs->pe_values[0] = pargs->pe_values[1] = 0;
    pargs->pe_indx = 0;
}

int
hpgl_PE(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    /*
     * To simplify the control structure here, we require that
     * the input buffer be large enough to hold 2 coordinates
     * in the base 32 encoding, i.e., 14 bytes.  We're counting on
     * there not being any whitespace within coordinate values....
     */
    const byte *p = pargs->source.ptr;
    const byte *rlimit = pargs->source.limit;
    /* count points to allow medium size paths, performance optimization
     * point_count_max should be smaller than input buffer
     */
    int point_count = 0;
    static const int point_count_max = 100;

    if (pargs->phase == 0) {
        /* After PE is executed, the previous plotting mode (absolute or
           relative) is restored.  If the finnal move is made with the pen up,
           the pen remains in the up position; otherwise the pen is left in
           the down position.  At least HP documented this bug. */
        hpgl_save_pen_state(pgls, &pgls->g.pen_state, hpgl_pen_relative);
        pargs->phase |= pe_entered;
        pgls->g.fraction_bits = 0;
        pe_clear_args(pargs);
    }
    while (p < rlimit) {
        byte ch = *(pargs->source.ptr = ++p);

        switch (ch & 127 /* per spec */ ) {
            case ';':
                hpgl_call(hpgl_update_carriage_return_pos(pgls));
                if (pargs->phase & pe_entered)
                    hpgl_restore_pen_state(pgls, &pgls->g.pen_state,
                                           hpgl_pen_relative);
                /* prevent paths from getting excessively large */
                if (!pgls->g.polygon_mode)
                    hpgl_call(hpgl_draw_current_path(pgls, hpgl_rm_vector));
                return 0;
            case ':':
                if_debug0m('i', pgls->memory, "\n  PE SP");
                {
                    int32 pen;
                    int ret = pe_args(pgls->memory, pargs, 1);

                    if (ret != gs_error_ok) {
                        if (ret == gs_error_NeedInput)
                            pargs->source.ptr = p - 1;
                        break;
                    }
                    pen = pargs->pe_values[0];
                    pe_clear_args(pargs);
                    if (!pgls->g.polygon_mode) {
                        hpgl_args_t args;

                        hpgl_args_set_int(&args, pen);
                        /* Note SP is illegal in polygon mode we must handle that here */
                        hpgl_call(hpgl_SP(&args, pgls));
                    }
                }
                p = pargs->source.ptr;
                continue;
            case '<':
                if_debug0m('i', pgls->memory, "\n  PE PU");
                pargs->phase |= pe_pen_up;
                continue;
            case '>':
                if_debug0m('i', pgls->memory, "\n  PE PD");
                {
                    int32 fbits;
                    int ret = pe_args(pgls->memory, pargs, 1);

                    if (ret != gs_error_ok) {
                        if (ret == gs_error_NeedInput)
                            pargs->source.ptr = p - 1;
                        break;
                    }
                    fbits = pargs->pe_values[0];
                    pe_clear_args(pargs);
                    if (fbits < -26 || fbits > 26)
                        return e_Range;
                    pgls->g.fraction_bits = fbits;
                }
                p = pargs->source.ptr;
                continue;
            case '=':
                if_debug0m('i', pgls->memory, "  PE ABS");
                pargs->phase |= pe_absolute;
                continue;
            case '7':
                if_debug0m('i', pgls->memory, "\n  PE 7bit");
                pargs->phase |= pe_7bit;
                continue;
            case ESC:
                /*
                 * This is something of a hack.  Apparently we're supposed
                 * to parse all PCL commands embedded in the GL/2 stream,
                 * and simply ignore everything except the 3 that end GL/2
                 * mode.  Instead, we simply stop parsing PE arguments as
                 * soon as we see an ESC.
                 */
                if (ch == ESC) {        /* (might be ESC+128) */
                    pargs->source.ptr = p - 1;  /* rescan ESC */
                    if (pargs->phase & pe_entered) {
                        hpgl_restore_pen_state(pgls, &pgls->g.pen_state,
                                               hpgl_pen_relative);
                    }
                    hpgl_call(hpgl_update_carriage_return_pos(pgls));
                    return 0;
                }
                /* falls through */
            default:
                if ((ch & 127) <= 32 || (ch & 127) == 127)
                    continue;
                pargs->source.ptr = p - 1;
                {
                    int32 xy[2];
                    hpgl_args_t args;
                    int32 fbits = pgls->g.fraction_bits;

                    if (pe_args(pgls->memory, pargs, 2) != gs_error_ok)
                        break;
                    xy[0] = pargs->pe_values[0];
                    xy[1] = pargs->pe_values[1];
                    pe_clear_args(pargs);
                    if (pargs->phase & pe_absolute)
                        pgls->g.relative_coords = hpgl_plot_absolute;
                    else
                        pgls->g.relative_coords = hpgl_plot_relative;
                    hpgl_args_set_real2(&args, pe_fixed2float(xy[0], fbits),
                                        pe_fixed2float(xy[1], fbits));
                    /* prevent paths from getting excessively large */
                    if (!pgls->g.polygon_mode
                        && point_count > point_count_max) {
                        hpgl_call(hpgl_draw_current_path
                                  (pgls, hpgl_rm_vector));
                        point_count = 0;
                    }
                    if (pargs->phase & pe_pen_up) {
                        hpgl_call(hpgl_PU(&args, pgls));
                    } else
                        hpgl_call(hpgl_PD(&args, pgls));
                    point_count++;
                }
                pargs->phase &= ~(pe_pen_up | pe_absolute);
                p = pargs->source.ptr;
                continue;
        }
        break;
    }
    return gs_error_NeedInput;
}
/* Get an encoded value from the input. */
static int
pe_args(const gs_memory_t * mem, hpgl_args_t * pargs, int count)
{
    const byte *p = pargs->source.ptr;
    const byte *rlimit = pargs->source.limit;
    int i;

    for (i = pargs->pe_indx; i < count; ++i) {

#define VALUE (pargs->pe_values[i])
#define SHIFT (pargs->pe_shift[i])

        for (;;) {
            int ch;

            if (p >= rlimit) {
                pargs->pe_indx = i;
                pargs->source.ptr = p;
                return gs_error_NeedInput;
            }
            ch = *++p;
            if ((ch & 127) <= 32 || (ch & 127) == 127)
                continue;
            if (SHIFT > 30)
                goto syntax_error;
            if (pargs->phase & pe_7bit) {
                ch -= 63;
                if (ch & ~63)
                    goto syntax_error;
                VALUE += (int32) (ch & 31) << SHIFT;
                SHIFT += 5;
                if (ch & 32)
                    break;
            } else {
                ch -= 63;
                if (ch & ~191)
                    goto syntax_error;
                VALUE += (int32) (ch & 63) << SHIFT;
                SHIFT += 6;
                if (ch & 128)
                    break;
            }
        }
        VALUE = (VALUE & 1 ? -(VALUE >> 1) : VALUE >> 1);
        if_debug1m('i', mem, "  [%ld]", (long)VALUE);
    }
    pargs->source.ptr = p;
    return gs_error_ok;
  syntax_error:
    /* Just ignore everything we've parsed up to this point. */
    pargs->source.ptr = p;
    return gs_error_syntaxerror;
}

/* PR dx,dy...; */
int
hpgl_PR(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    if (pgls->g.relative_coords != hpgl_plot_relative) {
        pgls->g.relative_coords = hpgl_plot_relative;
    }
    return hpgl_plot(pargs, pgls,
                     pgls->g.move_or_draw | hpgl_plot_relative,
                     false /* is PA command */ );
}

/* PU (d)x,(d)y...; */
int
hpgl_PU(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    int was = pgls->g.move_or_draw;

    pgls->g.move_or_draw = hpgl_plot_move;
    if (was == hpgl_plot_move)
        return hpgl_plot(pargs, pgls,
                     hpgl_plot_move | pgls->g.relative_coords,
                     true /* is PA command */ );
    else
        return hpgl_plot(pargs, pgls,
                     hpgl_plot_move | pgls->g.relative_coords,
                     false /* is PA command */ );
}

/* RT xinter,yinter,xend,yend[,chord]; */
int
hpgl_RT(hpgl_args_t * pargs, hpgl_state_t * pgls)
{
    hpgl_call(hpgl_arc_3_point(pargs, pgls, true));
    hpgl_call(hpgl_draw_arc(pgls));
    return 0;
}

/* Initialization */
static int
pgvector_do_registration(pcl_parser_state_t * pcl_parser_state,
                         gs_memory_t * mem)
{
    /* Register commands */
    DEFINE_HPGL_COMMANDS(mem)
        HPGL_COMMAND('A', 'A',
                     hpgl_AA,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared | hpgl_cdf_pcl_rtl_both),
        HPGL_COMMAND('A', 'R',
                     hpgl_AR,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared | hpgl_cdf_pcl_rtl_both),
        HPGL_COMMAND('A', 'T',
                     hpgl_AT,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared | hpgl_cdf_pcl_rtl_both),
        HPGL_COMMAND('B', 'R',
                     hpgl_BR,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('B', 'Z',
                     hpgl_BZ,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('C', 'I',
                     hpgl_CI,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared | hpgl_cdf_pcl_rtl_both),
        HPGL_COMMAND('P', 'A',
                     hpgl_PA,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('P', 'D',
                     hpgl_PD,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('P', 'E',
                     hpgl_PE,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both),
        HPGL_COMMAND('P', 'R',
                     hpgl_PR,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('P', 'U',
                     hpgl_PU,
                     hpgl_cdf_polygon | hpgl_cdf_pcl_rtl_both), /* argument pattern can repeat */
        HPGL_COMMAND('R', 'T',
                     hpgl_RT,
                     hpgl_cdf_polygon | hpgl_cdf_lost_mode_cleared |
                     hpgl_cdf_pcl_rtl_both),
    END_HPGL_COMMANDS
    return 0;
}

const pcl_init_t pgvector_init = {
    pgvector_do_registration, 0
};
