/* Copyright (C) 2001-2021 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.,  1305 Grant Avenue - Suite 200, Novato,
   CA 94945, U.S.A., +1(415)492-9861, for further information.
*/


/* pcursor.c - PCL5 cursor positioning commands */

#include "std.h"
#include "math_.h"
#include "pcommand.h"
#include "pcstate.h"
#include "pcdraw.h"
#include "pcpatxfm.h"
#include "pcfont.h"
#include "pcursor.h"
#include "pcpage.h"
#include "gscoord.h"
#include "pjtop.h"

/*
 * Hoizontal and vertical movement.
 *
 * This is one of the most confusing areas of PCL because the individual
 * movement commands and margins evolved at different times in the history
 * of PCL, and thus have different behavior. In the dicussion below, we
 * divide the various horizontal and vertical motion commands into groups,
 * and identify the interaction of each group with the corresponding horizontal
 * or vertical boundaries. (Note that, if the current print direciton is not
 * zero, what is called the "left" logical page boundary would, from the
 * point of view of the logical page, be given a different label.)
 *
 * Horizontal motion commmands (note: a movement "transitions" a boundary if
 * the current point before and after the movement is on opposite sides of
 * the boundary):
 *
 *     a. Horizontal position by column, decipoint, or PCL unit, in absolute
 *        or relative mode, and horizontal motion due to rasters:
 *
 *         "left" logical page boundary is used as origin for absolute positions
 *         movement to the left of the left logical page boundary is clamped
 *             to that boundary
 *         left text boundary is ignored
 *         right text boundary is ignored
 *         movement beyond the "right" logical page boundary is clamped to
 *             that boundary
 *
 *     b. Tab (always relative)
 *
 *         "left" logical boundary is irrelevant
 *         left text boundary is used as the origin for tab stops
 *         movement that transitions the right text boundary is clamped to
 *             that boundary
 *         movement beyond the "right" logical page boundary is clamped
 *             to that boundary
 *
 *     c. Character or space (code legal in symbol set up not occupied by a
 *        printable character; motion always relative)
 *
 *         "left" logical page boundary is irrelevant
 *         left text boundary is ignored
 *         if the character WOULD transition the right text boundary, ignore
 *             the character or issue a CR/LF sequence BEFORE printing the
 *             character, base on whether or not end-of-line wrapping is
 *             enabled (with one exception; see below)
 *         if the character WOULD transition the "right" logical page  boundary,
 *             ignore the character or issue a CR/LF sequence BEFORE printing
 *             the character, base on whether or not end-of-line wrapping is
 *             enabled
 *
 *        Note that only one of the latter two operations will be preformed if
 *        the logical and text margins are the same.
 *
 *        An exception is made in third case above in the event that a back-
 *        space was received immediately before the character to be rendered.
 *        In that case, the character is rendered regardless of the transition
 *        of the right text boundary.
 *
 *     d. Carriage return (always absolute)
 *
 *         "left" logical page boundary is irrelevant
 *         left text boundary is used as the new horizontal location
 *         right text boundary is irrelevant
 *         "right" logical page boundary is irrelevant
 *
 *     e. Back space
 *
 *         movement beyond the "left" logical page boundary is clamped to
 *             that boundary
 *         movement that would transition the left text boundary is clamped
 *             to that boundary
 *         right text boundary is ignored (this is contrary to HP's
 *             documentation, but empirically verified on several machines)
 *         "right" logical page boundary is irrelevant
 *
 * In addtion, any horizontal movement to the left will "break" the current
 * underline (this is significant for floating underlines).
 *
 * Vertical motion commands:
 *
 *     f. Vertical cursor position by decipoints or PCL units (but NOT by
 *        rows), both absolute and relative
 *
 *         movement beyond the "top" logical page boundary is clamped to
 *             that boundary
 *         top text boundary is used as the origin for absolute moves
 *         bottom text margin is ignored
 *         movement beyond the "bottom" logical page boundary is clamped to
 *             that boundary
 *
 *     g. Absolute (NOT relative) vertical cursor position by rows
 *
 *         "top" logical page boundary is irrelevant (can only be reached by
 *             relative moves)
 *         top text boundary, offset by 75% of the VMI distance, is used as
 *             the origin
 *         bottom text margin is ignored
 *         movement beyond the "bottom" logical page boundary is clamped to
 *             that boundary
 *
 *     h. Relative (NOT absolute) vertical cursor position by rows, and both
 *        line-feed and half line-feed when perforation skip is disabled
 *
 *         movement beyond the "top" logical page boundary is clamped to
 *             that boundary
 *         if and advance of n rows (n == 1 for LF) is requested, and only
 *             m additional rows can be accommodated on the current page,
 *             an implicit page ejection occurs and the cursor is positioned
 *             n - m rows below the "top" logical page boundary on the
 *             subsequent page; if the subsequent page will not accommodate
 *             n - m rows, the process is repeated
 *         after an implicit page eject (see below), the cursor is positioned
 *             75% of the VMI distance below the "top" logical page boundary plus
 *
 *         top text boundary is ignored
 *         bottom text boundary is ignored
 *         movement beyond the "bottom" page boundary causes an implicit
 *             page eject
 *
 *     i. Line-feed and halft line feed, when perforation skip is enabled
 *
 *         "top" logical page boundary is irrelevant
 *         after an implicit page eject (see below), the cursor is set 75% of
 *             the VMI distance below the top text boundary
 *         any movement that ends below the bottom text boundary causes a
 *             page eject (NB: this does not require a transition of the
 *             boundary, as is the case for the right text boundary)
 *         the "bottom" logical page boundary is irrelevant
 *
 *     j. Form feed
 *
 *         "top" logical page boundary is irrelevant
 *         the cursor is positioned 75% of the VMI distance below the top
 *             text boundary
 *         bottom text boundary is irrelevant
 *         "bottom" logical page boundary is irrelevant
 *
 *
 *     k. for a relative vertical motion command relative movement can extend
 *        to the next logical page's lower boundary, where it is clamped.
 *        This should could be grouped with item h.
 *
 * Wow - 11 different forms to accommodate.
 *
 * The special handling required by character and space induced horizontal
 * movement is handled in pctext.c (pcl_show_chars); all other movement is
 * handled in this file.
 */

#define HOME_X(pcs) (pcs->margins.left)
#define DEFAULT_Y_START(pcs) ((3L * pcs->vmi_cp) / 4L)
#define HOME_Y(pcs) (pcs->margins.top + DEFAULT_Y_START(pcs))

static inline bool
pcl_cursor_at_home_pos(pcl_state_t * pcs)
{
    return ((pcs->cap.y == HOME_Y(pcs)) && (pcs->cap.x == HOME_X(pcs)));
}

int
pcl_set_cap_x(pcl_state_t * pcs, coord x, bool relative, bool use_margins)
{
    int code = 0;
    coord old_x = pcs->cap.x;

    if (relative)
        x += pcs->cap.x;

    /* the horizontal text margins are only interesting in transition */
    if (use_margins) {
        coord min_x = pcs->margins.left;
        coord max_x = pcs->margins.right;

        if ((old_x >= min_x) && (x < min_x))
            x = min_x;
        else if ((old_x <= max_x) && (x > max_x))
            x = max_x;
    }

    /* the logical page bounds always apply */
    x = (x > pcs->xfm_state.pd_size.x ? pcs->xfm_state.pd_size.x
         : (x < 0L ? 0L : x));

    /* leftward motion "breaks" an underline */
    if (x < old_x) {
        code = pcl_break_underline(pcs);
        pcs->cap.x = x;
        pcl_continue_underline(pcs);
    } else
        pcs->cap.x = x;

    return code;
}

int
pcl_set_cap_y(pcl_state_t * pcs,
              coord y,
              bool relative,
              bool use_margins, bool by_row, bool by_row_command)
{
    int code = 0;
    coord lim_y = pcs->xfm_state.pd_size.y;
    coord max_y = pcs->margins.top + pcs->margins.length;
    bool page_eject = by_row && relative;

    /* this corresponds to rule 'k' above. */
    if (relative && by_row_command) {
        /* calculate the advance to the next logical page bound.  Note
           margins are false if by_row_command is true. */
        coord advance_max = 2 * lim_y - pcs->cap.y;

        /* clamp */
        y = (y < advance_max ? y : advance_max + HOME_Y(pcs));
    }

    /* adjust the vertical position provided */
    if (relative)
        y += pcs->cap.y;
    else
        y += (by_row ? HOME_Y(pcs) : pcs->margins.top);

    /* vertical moves always "break" underlines */
    code = pcl_break_underline(pcs);
    if (code < 0)
        return code;

    max_y = (use_margins ? max_y : lim_y);
    if (y < 0L)
        pcs->cap.y = 0L;
    else if (y <= max_y)
        pcs->cap.y = y;
    else if (!page_eject)
        pcs->cap.y = (y <= lim_y ? y : lim_y);
    else {
        coord vmi_cp = pcs->vmi_cp;
        coord y0 = pcs->cap.y;

        while (y > max_y) {
            int code = pcl_end_page_always(pcs);

            if (code < 0)
                return code;
            y -= (y0 <= max_y ? max_y : y0);
            y0 = (use_margins ? HOME_Y(pcs) : DEFAULT_Y_START(pcs));

            /* if one VMI distance or less remains, always exit */
            if ((vmi_cp == 0) || (y <= vmi_cp)) {
                y = y0;
                break;
            }

            /* otherwise, round to a multiple of VMI distance */
            y += y0 - 1 - ((y - 1) % vmi_cp);
        }
        pcs->cap.y = y;
    }

    pcl_continue_underline(pcs);
    return code;
}

static inline float
motion_args(pcl_args_t * pargs, bool truncate)
{
    float arg = float_arg(pargs);

    if (truncate)
        arg = floor(arg);
    return arg;
}

/* some convenient short-hand for the cursor movement commands */

static inline int
do_horiz_motion(pcl_args_t * pargs,
                pcl_state_t * pcs, coord mul, bool truncate_arg)
{
    pcs->cursor_moved = true;
    return pcl_set_cap_x(pcs, (coord) (motion_args(pargs, truncate_arg) * mul),
                  arg_is_signed(pargs), false);
}

static inline int
do_vertical_move(pcl_state_t * pcs, pcl_args_t * pargs, float mul,
                 bool use_margins, bool by_row, bool by_row_command,
                 bool truncate_arg)
{
    pcs->cursor_moved = true;
    return pcl_set_cap_y(pcs,
                         (coord) (motion_args(pargs, truncate_arg) * mul),
                         arg_is_signed(pargs), use_margins, by_row,
                         by_row_command);
}

/*
 * Control character action implementation.
 *
 * These routines perform just the individual actions. The control character
 * routines may invoke several of these, based on the selected line termination
 * setting.
 *
 * do_CR and do_LF are exported for use by the text manipulation routines and
 * the display functions.
 *
 * Note: CR always "breaks" an underline, even if it is a movement to the right.
 */
int
pcl_do_CR(pcl_state_t * pcs)
{
    int code =  pcl_break_underline(pcs);
    if (code < 0)
        return code;

    code = pcl_set_cap_x(pcs, pcs->margins.left, false, false);
    if (code >= 0) {
        pcl_continue_underline(pcs);
        pcs->cursor_moved = true;
    }

    return code;
}

int
pcl_do_LF(pcl_state_t * pcs)
{
    pcs->cursor_moved = true;
    return pcl_set_cap_y(pcs,
                         pcs->vmi_cp,
                         true, (pcs->perforation_skip == 1), true, false);
}

/*
 * Unconditionally feed a page, and move the the "home" verical position on
 * the followin page.
 */
int
pcl_do_FF(pcl_state_t * pcs)
{
    int code = pcl_end_page_always(pcs);

    if (code >= 0) {
        code = pcl_set_cap_y(pcs, 0L, false, false, true, false);
        pcl_continue_underline(pcs);    /* (after adjusting y!) */
    }
    return code;
}

/*
 * Return the cursor to its "home" position
 */
int
pcl_home_cursor(pcl_state_t * pcs)
{
    int code = pcl_set_cap_x(pcs, pcs->margins.left, false, false);
    if (code < 0)
        return code;
    return pcl_set_cap_y(pcs, 0L, false, false, true, false);
}

int pcl_update_hmi_cp(pcl_state_t * pcs)
{
    return ((pcs->hmi_cp == HMI_DEFAULT) ? pcl_updated_hmi(pcs) : 0);
}

/*
 * Update the HMI by recomputing it from the font.
 */
int
pcl_updated_hmi(pcl_state_t * pcs)
{
    coord hmi;
    const pcl_font_selection_t *pfs =
        &(pcs->font_selection[pcs->font_selected]);
    int code = pcl_recompute_font(pcs, false);
    const pl_font_t *plfont = pcs->font;

    if (code < 0)
        return code;     /* bad news; don't mark the HMI as valid. */

    /* we check for typeface == 0 here (lineprinter) because we
       frequently simulate lineprinter with a scalable truetype
       font */
    if (pl_font_is_scalable(plfont) && plfont->params.typeface_family != 0) {
        if (plfont->params.proportional_spacing)
            /* Scale the font's pitch by the requested height. */
            hmi =
                (coord) (pl_fp_pitch_cp(&plfont->params) / 10.0 *
                         pfs->params.height_4ths / 4);
        else
            hmi = (coord) pl_fp_pitch_cp(&(pfs->params));
    } else
        hmi = (coord) pl_fp_pitch_cp(&(plfont->params));

    /*
     * Round to a multiple of the unit of measure (see the "PCL 5 Printer
     * LanguageTechnical Reference Manual", October 1992 ed., page 5-22.
     */
    hmi = hmi + pcs->uom_cp / 2;
    pcs->hmi_cp = hmi - (hmi % pcs->uom_cp);
    return code;
}

/* Commands */

/*
 * ESC & k <x> H
 *
 * Set horizontal motion index.
 */
static int
set_horiz_motion_index(pcl_args_t * pargs, pcl_state_t * pcs)
{
    /* HMI in 120 units converted to 7200 units with roundup */
    pcs->hmi_cp = (coord) ((fabs(float_arg(pargs)) * 60.0) + 0.5);
    return 0;
}

/*
 * ESC & l <y> C
 *
 * Set vertical motion index.
 *
 * Contrary to HP's documentation ("PCL 5 Printer Language Technical Reference
 * Manual", October 1992 ed., p. 5-24), this command is NOT ignored if the
 * requested VMI is greater than the page length.
 *
 * Apparently this problem has been fixed in the Color Laserjet 4600.
 * For the old behavior undefine the next definition.
 */

#define HP_VERT_MOTION_NEW
static int
set_vert_motion_index(pcl_args_t * pargs, pcl_state_t * pcs)
{
    int code = 0;
    /* LMI :== 48.0 / lpi;  ie 0.16 = 48/300;
     * convert to pcl_coord_scale (7200), roundup the float prior to truncation.
     */
    coord vcp = (coord) ((fabs(float_arg(pargs)) * 7200.0 / 48.0) + 0.5);
    bool cursor_at_home = pcl_cursor_at_home_pos(pcs);

#ifdef HP_VERT_MOTION_NEW
    if (vcp <= pcs->xfm_state.pd_size.y)
#endif
        pcs->vmi_cp = vcp;

    /* If the cursor has not been set, the new default position should
       reflect the change in vmi.  This behavior is quite unusual and
       probably is not the way HP intended PCL devices to behave.  In
       the spec it is reported the VMI should take effect if the
       printer is on the first line but we've found moving the x
       coordinate (horizontal position) cause the command to not take
       effect. */
    if (cursor_at_home)
        code = pcl_home_cursor(pcs);
    return code;
}

#undef HP_VERT_MOTION_NEW

/*
 * ESC & l <lpi> D
 *
 * Set line spacing. Though it is not documented anywhere, various HP devices
 * agree that a zero operand specifies 12 lines per inch (NOT the default).
 */
static int
set_line_spacing(pcl_args_t * pargs, pcl_state_t * pcs)
{
    int code = 0;
    uint lpi = uint_arg(pargs);
    bool cursor_at_home = pcl_cursor_at_home_pos(pcs);

    if (lpi == 0)               /* 0 ==> 12 lines per inch */
        lpi = 12;
    if ((48 % lpi) == 0)        /* lpi must divide 48 */
        pcs->vmi_cp = inch2coord(1.0 / lpi);
    if (cursor_at_home)
        code = pcl_home_cursor(pcs);
    return code;
}

/*
 * ESC & k G
 */
static int
set_line_termination(pcl_args_t * pargs, pcl_state_t * pcs)
{
    uint ui = uint_arg(pargs);

    if (ui <= 3)
        pcs->line_termination = ui;
    return 0;
}

/*
 * ESC & a <cols> C
 */
static int
horiz_cursor_pos_columns(pcl_args_t * pargs, pcl_state_t * pcs)
{
    int code = pcl_update_hmi_cp(pcs);
    if (code < 0)
        return code;

    return do_horiz_motion(pargs, pcs, pcs->hmi_cp, false);
}

/*
 * ESC & a <dp> H
 */
static int
horiz_cursor_pos_decipoints(pcl_args_t * pargs, pcl_state_t * pcs)
{
    return do_horiz_motion(pargs, pcs, (coord) 10.0, false);
}

/*
 * ESC * p <units> X
 */
static int
horiz_cursor_pos_units(pcl_args_t * pargs, pcl_state_t * pcs)
{
    return do_horiz_motion(pargs, pcs, pcs->uom_cp, true);
}

/*
 * CR
 */
static int
cmd_CR(pcl_args_t * pargs,      /* ignored */
       pcl_state_t * pcs)
{
    int code = pcl_do_CR(pcs);
    if (code < 0)
        return code;
    return ((pcs->line_termination & 1) != 0 ? pcl_do_LF(pcs) : 0);
}

/*
 * BS
 */
static int
cmd_BS(pcl_args_t * pargs,      /* ignored */
       pcl_state_t * pcs)
{
    pcs->last_was_BS = true;
    pcs->cursor_moved = true;
    return pcl_set_cap_x(pcs, (coord) - pcs->last_width, true, true);
}

/*
 * HT
 *
 * Tabs occur at ever 8 columns, measure from the left text margin.
 */
static int
cmd_HT(pcl_args_t * pargs,      /* ignored */
       pcl_state_t * pcs)
{
    coord x = pcs->cap.x - pcs->margins.left;
    coord tab;

    if (x < 0)
        x = -x;
    else
    {
        int code = pcl_update_hmi_cp(pcs);
        if (code < 0)
            return code;

        if ((tab = 8 * pcs->hmi_cp) > 0)
            x = tab - (x % tab);
        else
            x = 0L;
    }

    pcs->cursor_moved = true;
    return pcl_set_cap_x(pcs, x, true, true);
}

/*
 * ESC & a <rows> R
 */
static int
vert_cursor_pos_rows(pcl_args_t * pargs, pcl_state_t * pcs)
{
    return do_vertical_move(pcs, pargs, (float)pcs->vmi_cp, false, true, true,
                            false);
}

/*
 * ESC & a <dp> V
 */
static int
vert_cursor_pos_decipoints(pcl_args_t * pargs, pcl_state_t * pcs)
{
    return do_vertical_move(pcs, pargs, 10.0, false, false, false, false);
}

/*
 * ESC * p <units> Y
 */
static int
vert_cursor_pos_units(pcl_args_t * pargs, pcl_state_t * pcs)
{
    return do_vertical_move(pcs, pargs, (float)pcs->uom_cp, false, false,
                            false, true);
}

/*
 * ESC =
 */
static int
half_line_feed(pcl_args_t * pargs,      /* ignored */
               pcl_state_t * pcs)
{
    pcs->cursor_moved = true;
    return pcl_set_cap_y(pcs,
                         pcs->vmi_cp / 2,
                         true, (pcs->perforation_skip == 1), true, false);
}

/*
 * LF
 */
static int
cmd_LF(pcl_args_t * pargs,      /* ignored */
       pcl_state_t * pcs)
{
    if ((pcs->line_termination & 2) != 0) {
        int code = pcl_do_CR(pcs);
        if (code < 0)
            return code;
    }
    return pcl_do_LF(pcs);
}

/*
 * FF
 */
static int
cmd_FF(pcl_args_t * pargs,      /* ignored */
       pcl_state_t * pcs)
{
    if ((pcs->line_termination & 2) != 0) {
        int code = pcl_do_CR(pcs);
        if (code < 0)
            return code;
    }
    return pcl_do_FF(pcs);
}

/*
 * ESC & f <pp_enum> S
 *
 * Contrary to what is indicated in the "PCL 5 Printer Language Technical
 * Reference Manual", October 1992 ed., p. 6-16, pushd cursors are stored
 * in logical page space, not device space.
 */
static int
push_pop_cursor(pcl_args_t * pargs, pcl_state_t * pcs)
{
    int code = 0;
    int type = uint_arg(pargs);

    if ((type == 0) && (pcs->cursor_stk_size < countof(pcs->cursor_stk))) {
        gs_point *ppt = &(pcs->cursor_stk[pcs->cursor_stk_size++]);

        ppt->x = (double)pcs->cap.x;
        ppt->y = (double)pcs->cap.y;
        gs_point_transform(ppt->x, ppt->y, &(pcs->xfm_state.pd2lp_mtx), ppt);

    } else if ((type == 1) && (pcs->cursor_stk_size > 0)) {
        gs_point *ppt = &(pcs->cursor_stk[--pcs->cursor_stk_size]);
        gs_matrix lp2pd;

        pcl_invert_mtx(&(pcs->xfm_state.pd2lp_mtx), &lp2pd);
        gs_point_transform(ppt->x, ppt->y, &lp2pd, ppt);
        code = pcl_set_cap_x(pcs, (coord) ppt->x, false, false);
        if (code < 0)
            return code;
        code = pcl_set_cap_y(pcs,
                      (coord) ppt->y - pcs->margins.top,
                      false, false, false, false);
    }

    return code;
}

static int
pcursor_do_copy(pcl_state_t * psaved,
                const pcl_state_t * pcs, pcl_copy_operation_t operation)
{
    int i;

    /* don't restore the current cap.  The cap is not part of the
       state */
    if (operation & pcl_copy_after) {
        psaved->cap = pcs->cap;

        /* cursor stack isn't part of the state, either */
        for (i = 0; i < pcs->cursor_stk_size; ++i) {
            psaved->cursor_stk[i] = pcs->cursor_stk[i];
        }
        psaved->cursor_stk_size = pcs->cursor_stk_size;

        /* NB doesn't belong here */
        psaved->page_marked = pcs->page_marked;
    }
    return 0;
}

/* If PJL FORMLINES is set use we always use that value to derive VMI,
   otherwise we use the HP documented default.  This may not be
   exactly what HP does, the state interaction between formlines,
   paper size, orientation each of which can have different values in
   PJL and PCL is quite difficult to figure out, we believe this is
   correct for the vast majority of cases. */
coord
pcl_vmi_default(pcl_state_t * pcs)
{
    coord vmi;

    if (!pjl_proc_compare(pcs->pjls,
                          pjl_proc_get_envvar(pcs->pjls,
                                              "FORMLINES_SET"), "ON")) {
        int value = pjl_proc_vartoi(pcs->pjls, pjl_proc_get_envvar(pcs->pjls, "formlines"));
        vmi = (pcs->margins.length / (value < 5 ? 5 : (value > 128 ? 128 : value)));
    } else {
        vmi = inch2coord(8.0 / 48.0);
    }
    return vmi;
}

/*
 * Initialization
 */
static int
pcursor_do_registration(pcl_parser_state_t * pcl_parser_state,
                        gs_memory_t * pmem)
{

    DEFINE_CLASS('&') {
        'k', 'H',
            PCL_COMMAND("Horizontal Motion Index",
                        set_horiz_motion_index, pca_neg_ok | pca_big_clamp)
    }, {
        'l', 'C',
            PCL_COMMAND("Vertical Motion Index",
                        set_vert_motion_index, pca_neg_ok | pca_big_ignore)
    }, {
        'l', 'D',
            PCL_COMMAND("Line Spacing",
                        set_line_spacing, pca_neg_ok | pca_big_ignore)
    }, {
        'k', 'G',
            PCL_COMMAND("Line Termination",
                        set_line_termination, pca_neg_ok | pca_big_ignore)
    }, {
        'a', 'C',
            PCL_COMMAND("Horizontal Cursor Position Columns",
                        horiz_cursor_pos_columns, pca_neg_ok | pca_big_ok)
    }, {
        'a', 'H',
            PCL_COMMAND("Horizontal Cursor Position Decipoints",
                        horiz_cursor_pos_decipoints, pca_neg_ok | pca_big_ok)
    }, {
        'a', 'R',
            PCL_COMMAND("Vertical Cursor Position Rows",
                        vert_cursor_pos_rows, pca_neg_ok | pca_big_clamp)
    }, {
        'a', 'V',
            PCL_COMMAND("Vertical Cursor Position Decipoints",
                        vert_cursor_pos_decipoints, pca_neg_ok | pca_big_ok)
    }, {
        'f', 'S',
            PCL_COMMAND("Push/Pop Cursor",
                        push_pop_cursor, pca_neg_ok | pca_big_ignore)
    }, END_CLASS DEFINE_CLASS('*') {
        'p', 'X',
            PCL_COMMAND("Horizontal Cursor Position Units",
                        horiz_cursor_pos_units,
                        pca_neg_ok | pca_big_ok | pca_in_rtl)
    }, {
        'p', 'Y',
            PCL_COMMAND("Vertical Cursor Position Units",
                        vert_cursor_pos_units,
                        pca_neg_ok | pca_big_ok | pca_in_rtl)
    }, END_CLASS DEFINE_CONTROL(CR, "CR", cmd_CR)
        DEFINE_CONTROL(BS, "BS", cmd_BS)
        DEFINE_CONTROL(HT, "HT", cmd_HT)
        DEFINE_ESCAPE('=', "Half Line Feed", half_line_feed)
        DEFINE_CONTROL(LF, "LF", cmd_LF)
        DEFINE_CONTROL(FF, "FF", cmd_FF)

        return 0;
}

static int
pcursor_do_reset(pcl_state_t * pcs, pcl_reset_type_t type)
{
    static const uint mask = (pcl_reset_initial
                              | pcl_reset_printer | pcl_reset_overlay);

    if ((type & mask) == 0)
        return 0;

    pcs->line_termination = 0;
    pcs->hmi_cp = HMI_DEFAULT;
    pcs->vmi_cp = VMI_DEFAULT;

    if ((type & pcl_reset_overlay) == 0) {
        pcs->cursor_stk_size = 0;

        /*
         * If this is an initial reset, make sure underlining is
         * disabled (homing the cursor may cause an underline to be
         * put out.  And provide reasonable initial values for the
         * cap.
         */
        if ((type & pcl_reset_initial) != 0) {
            pcs->underline_enabled = false;
            pcs->cap.x = pcs->cap.y = 0;
        }
    }

    return pcl_home_cursor(pcs);
}

const pcl_init_t pcursor_init =
    { pcursor_do_registration, pcursor_do_reset, pcursor_do_copy };
