diff options
Diffstat (limited to 'pcl/pcursor.c')
-rw-r--r-- | pcl/pcursor.c | 892 |
1 files changed, 892 insertions, 0 deletions
diff --git a/pcl/pcursor.c b/pcl/pcursor.c new file mode 100644 index 000000000..b0700b7b6 --- /dev/null +++ b/pcl/pcursor.c @@ -0,0 +1,892 @@ +/* Portions Copyright (C) 2001 artofcode LLC. + Portions Copyright (C) 1996, 2001 Artifex Software Inc. + Portions Copyright (C) 1988, 2000 Aladdin Enterprises. + This software is based in part on the work of the Independent JPEG Group. + All Rights Reserved. + + This software is distributed under license and may not be copied, modified + or distributed except as expressly authorized under the terms of that + license. Refer to licensing information at http://www.artifex.com/ or + contact Artifex Software, Inc., 101 Lucas Valley Road #110, + San Rafael, CA 94903, (415)492-9861, for further information. */ +/*$Id$ */ + +/* 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)) + + void +pcl_set_cap_x( + pcl_state_t * pcs, + coord x, + bool relative, + bool use_margins +) +{ + 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) { + pcl_break_underline(pcs); + pcs->cap.x = x; + pcl_continue_underline(pcs); + } else + pcs->cap.x = x; +} + + int +pcl_set_cap_y( + pcl_state_t * pcs, + coord y, + bool relative, + bool use_margins, + bool by_row, + bool by_row_command +) +{ + 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 */ + pcl_break_underline(pcs); + + 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 0; +} + +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 void +do_horiz_motion( + pcl_args_t *pargs, + pcl_state_t *pcs, + coord mul, + bool truncate_arg +) +{ + pcl_set_cap_x(pcs, motion_args(pargs, truncate_arg) * mul, arg_is_signed(pargs), false); + return; +} + + +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) +{ + return pcl_set_cap_y(pcs, 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. + */ + void +pcl_do_CR( + pcl_state_t * pcs +) +{ + pcl_break_underline(pcs); + pcl_set_cap_x(pcs, pcs->margins.left, false, false); + pcl_continue_underline(pcs); +} + + int +pcl_do_LF( + pcl_state_t * pcs +) +{ + 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 + */ + void +pcl_home_cursor( + pcl_state_t * pcs +) +{ + pcl_set_cap_x(pcs, pcs->margins.left, false, false); + pcl_set_cap_y(pcs, 0L, false, false, true, false); +} + +/* + * Update the HMI by recomputing it from the font. + */ + coord +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); + const pl_font_t * plfont = pcs->font; + + if (code < 0) + return pcs->hmi_cp; /* 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 = pl_fp_pitch_cp(&plfont->params) * pfs->params.height_4ths / 4; + else + hmi = pl_fp_pitch_cp(&(pfs->params)); + } else + hmi = pl_fp_pitch_cp(&(plfont->params)) * 10.0; + + /* + * 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; + return pcs->hmi_cp = hmi - (hmi % pcs->uom_cp); +} + + +/* 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 +) +{ + /* LMI :== 48.0 / lpi; ie 0.16 = 48/300; + * convert to pcl_coord_scale (7200), roundup the float prior to truncation. + */ + coord vcp = ((fabs(float_arg(pargs)) * 7200.0 / 48.0) + 0.5); +#ifdef HP_VERT_MOTION_NEW + if (vcp <= pcs->xfm_state.pd_size.y) +#endif + pcs->vmi_cp = vcp; + return 0; +} + +#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 +) +{ + uint lpi = uint_arg(pargs); + + 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); + return 0; +} + +/* + * 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 +) +{ + do_horiz_motion(pargs, pcs, pcl_hmi(pcs), false); + return 0; +} + +/* + * ESC & a <dp> H + */ + static int +horiz_cursor_pos_decipoints( + pcl_args_t * pargs, + pcl_state_t * pcs +) +{ + do_horiz_motion(pargs, pcs, 10.0, false); + return 0; +} + +/* + * ESC * p <units> X + */ + static int +horiz_cursor_pos_units( + pcl_args_t * pargs, + pcl_state_t * pcs +) +{ + if ( pcs->personality == rtl ) + dprintf("Warning: device/resolution dependent units used\n" ); + do_horiz_motion(pargs, pcs, pcs->uom_cp, true); + return 0; +} + +/* + * CR + */ + static int +cmd_CR( + pcl_args_t * pargs, /* ignored */ + pcl_state_t * pcs +) +{ + pcl_do_CR(pcs); + 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 +) +{ + pcl_set_cap_x(pcs, -pcs->last_width, true, true); + pcs->last_was_BS = true; + return 0; +} + +/* + * 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 if ((tab = 8 * pcl_hmi(pcs)) > 0) + x = tab - (x % tab); + else + x = 0L; + pcl_set_cap_x(pcs, x, true, true); + return 0; +} + + +/* + * ESC & a <rows> R + */ + static int +vert_cursor_pos_rows( + pcl_args_t * pargs, + pcl_state_t * pcs +) +{ + return do_vertical_move(pcs, pargs, 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 +) +{ + if ( pcs->personality == rtl ) + dprintf("Warning: device/resolution dependent units used\n" ); + return do_vertical_move(pcs, pargs, pcs->uom_cp, false, false, false, true); +} + +/* + * ESC = + */ + static int +half_line_feed( + pcl_args_t * pargs, /* ignored */ + pcl_state_t * pcs +) +{ + 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) + pcl_do_CR(pcs); + 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) + pcl_do_CR(pcs); + 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 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); + pcl_set_cap_x(pcs, (coord)ppt->x, false, false); + pcl_set_cap_y( pcs, + (coord)ppt->y - pcs->margins.top, + false, + false, + false, + false + ); + } + + return 0; +} + +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; +} + +/* + * 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 void +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; + + pcs->line_termination = 0; + pcs->hmi_cp = HMI_DEFAULT; + pcs->vmi_cp = pcs->margins.length + / pjl_proc_vartoi(pcs->pjls, pjl_proc_get_envvar(pcs->pjls, "formlines")); + 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; + /* WRONG why is the cap set to 0 and then the + pcl_home_cursor(pcs) */ + pcs->cap.x = pcs->cap.y = 0; + } + } + pcl_home_cursor(pcs); +} + +const pcl_init_t pcursor_init = { pcursor_do_registration, pcursor_do_reset, pcursor_do_copy }; |