/* * VARCem Virtual ARchaeological Computer EMulator. * An emulator of (mostly) x86-based PC systems and devices, * using the ISA,EISA,VLB,MCA and PCI system buses, roughly * spanning the era between 1981 and 1995. * * Implementation of the Generic ESC/P 2 Dot-Matrix printer. * * Authors: Michael Drüing, * Fred N. van Kempen, * * Based on code by Frederic Weymann (originally for DosBox.) * * Copyright 2018-2019 Michael Drüing. * Copyright 2019 Fred N. van Kempen. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the entire * above notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names * of its contributors may be used to endorse or promote * products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include FT_FREETYPE_H #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include "cpu.h" #include <86box/machine.h> #include <86box/timer.h> #include <86box/mem.h> #include <86box/rom.h> #include <86box/pit.h> #include <86box/path.h> #include <86box/plat.h> #include <86box/ui.h> #include <86box/lpt.h> #include <86box/video.h> #include <86box/png_struct.h> #include <86box/printer.h> #include <86box/prt_devs.h> #include <86box/prt_papersizes.h> enum { LANG_EX1000 = 0, // last printer with ESC i and j LANG_9PIN, LANG_ESCP, // also known as 24/48-pin LANG_ESCP2 }; enum { PAPER_LETTER = 0, PAPER_A4, PAPER_LEGAL_SIDE, PAPER_B4_SIDE }; /* Default page values (for now.) */ #define COLOR_BLACK 7 << 5 #define PAGE_CPI 10.0 /* standard310 cpi */ #define PAGE_LPI 6.0 /* standard 6 lpi */ /* FreeType library handles - global so they can be shared. */ FT_Library ft_lib = NULL; /* The fonts. */ enum { FONT_DEFAULT = 0, FONT_ROMAN, FONT_SANSSERIF, FONT_COURIER, FONT_SCRIPT, FONT_OCRA, FONT_OCRB }; /* Font styles. */ #define STYLE_PROP 0x0001 #define STYLE_CONDENSED 0x0002 #define STYLE_BOLD 0x0004 #define STYLE_DOUBLESTRIKE 0x0008 #define STYLE_DOUBLEWIDTH 0x0010 #define STYLE_ITALICS 0x0020 #define STYLE_UNDERLINE 0x0040 #define STYLE_SUPERSCRIPT 0x0080 #define STYLE_SUBSCRIPT 0x0100 #define STYLE_STRIKETHROUGH 0x0200 #define STYLE_OVERSCORE 0x0400 #define STYLE_DOUBLEWIDTHONELINE 0x0800 #define STYLE_DOUBLEHEIGHT 0x1000 /* Underlining styles. */ #define SCORE_NONE 0x00 #define SCORE_SINGLE 0x01 #define SCORE_DOUBLE 0x02 #define SCORE_SINGLEBROKEN 0x05 #define SCORE_DOUBLEBROKEN 0x06 /* Print quality. */ #define QUALITY_DRAFT 0x01 #define QUALITY_LQ 0x02 /* Typefaces. */ enum { TYPEFACE_ROMAN = 0, TYPEFACE_SANSSERIF, TYPEFACE_COURIER, TYPEFACE_PRESTIGE, TYPEFACE_SCRIPT, TYPEFACE_OCRB, TYPEFACE_OCRA, TYPEFACE_ORATOR, TYPEFACE_ORATORS, TYPEFACE_SCRIPTC, TYPEFACE_ROMANT, TYPEFACE_SANSSERIFH, TYPEFACE_SVBUSABA = 30, TYPEFACE_SVJITTRA }; /* Some helper macros. */ #define PARAM16(x) (dev->esc_parms[x + 1] * 256 + dev->esc_parms[x]) #define PIXX ((unsigned) floor(dev->curr_x * dev->dpi + 0.5)) #define PIXY ((unsigned) floor(dev->curr_y * dev->dpi + 0.5)) typedef struct psurface_t { int8_t dirty; /* has the page been printed on? */ char pad; uint16_t w; /* size and pitch //INFO */ uint16_t h; uint16_t pitch; uint8_t *pixels; /* grayscale pixel data */ } psurface_t; typedef struct escp_t { const char *name; void *lpt; pc_timer_t pulse_timer; pc_timer_t timeout_timer; int lang; int paper_size; char page_fn[260]; uint8_t color; /* page data (TODO: make configurable) */ double page_width; /* all in inches */ double page_height; double left_margin; double top_margin; double right_margin; double bottom_margin; uint16_t dpi; double cpi; /* defined chars per inch */ double lpi; /* defined lines per inch */ /* font data */ double actual_cpi; /* actual cpi as with current font */ double linespacing; /* in inch */ double hmi; /* hor. motion index (inch); overrides CPI */ /* tabstops */ double horizontal_tabs[32]; uint8_t num_horizontal_tabs; double vertical_tabs[16]; int8_t num_vertical_tabs; /* bit graphics data */ uint16_t bg_h_density; /* in dpi */ uint16_t bg_v_density; /* in dpi */ int8_t bg_adjacent; /* print adjacent pixels (ignored) */ uint8_t bg_bytes_per_column; uint16_t bg_remaining_bytes; /* #bytes left before img is complete */ uint8_t bg_column[6]; /* #bytes of the current and last col */ uint8_t bg_bytes_read; /* #bytes read so far for current col */ /* handshake data */ uint8_t data; uint8_t ack; uint8_t select; uint8_t busy; uint8_t int_pending; uint8_t error; uint8_t autofeed; /* ESC command data */ int8_t esc_seen; /* set to 1 if an ESC char was seen */ int8_t fss_seen; uint16_t esc_pending; /* in which ESC command are we */ uint8_t esc_parms_req; uint8_t esc_parms_curr; uint8_t esc_parms[20]; /* 20 should be enough for everybody */ /* internal page data */ char fontpath[1024]; char pagepath[1024]; psurface_t *page; double curr_x; /* print head position (x, inch) */ double curr_y; /* print head position (y, inch) */ uint16_t current_font; FT_Face fontface; int8_t lq_typeface; uint16_t font_style; uint8_t print_quality; uint8_t font_score; double extra_intra_space; /* extra spacing between chars (inch) */ /* other internal data */ uint16_t char_tables[4]; /* the character tables for ESC t */ uint8_t curr_char_table; /* the active char table index */ uint16_t curr_cpmap[256]; /* current ASCII->Unicode map table */ int8_t multipoint_mode; /* multipoint mode, ESC X */ double multipoint_size; /* size of font, in points */ double multipoint_cpi; /* chars per inch in multipoint mode */ uint8_t density_k; /* density modes for ESC K/L/Y/Z */ uint8_t density_l; uint8_t density_y; uint8_t density_z; int8_t print_upper_control; /* ESC 6, ESC 7 */ int8_t print_everything_count; /* for ESC ( ^ */ double defined_unit; /* internal unit for some ESC/P * commands. -1 = use default */ uint8_t msb; /* MSB mode, -1 = off */ uint8_t ctrl; PALETTE palcol; } escp_t; /* Codepage table, needed for ESC t ( */ static const uint16_t codepages[15] = { 0, 437, 932, 850, 851, 853, 855, 860, 863, 865, 852, 857, 862, 864, 866 }; /* "patches" to the codepage for the international charsets * these bytes patch the following 12 positions of the char table, in order: * 0x23 0x24 0x40 0x5b 0x5c 0x5d 0x5e 0x60 0x7b 0x7c 0x7d 0x7e */ static const uint16_t intCharSets[15][12] = { { 0x0023, 0x0024, 0x0040, 0x005b, 0x005c, 0x005d, /* 0 USA */ 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, { 0x0023, 0x0024, 0x00e0, 0x00ba, 0x00e7, 0x00a7, /* 1 France */ 0x005e, 0x0060, 0x00e9, 0x00f9, 0x00e8, 0x00a8}, { 0x0023, 0x0024, 0x00a7, 0x00c4, 0x00d6, 0x00dc, /* 2 Germany */ 0x005e, 0x0060, 0x00e4, 0x00f6, 0x00fc, 0x00df}, { 0x00a3, 0x0024, 0x0040, 0x005b, 0x005c, 0x005d, /* 3 UK */ 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, { 0x0023, 0x0024, 0x0040, 0x00c6, 0x00d8, 0x00c5, /* 4 Denmark I */ 0x005e, 0x0060, 0x00e6, 0x00f8, 0x00e5, 0x007e}, { 0x0023, 0x00a4, 0x00c9, 0x00c4, 0x00d6, 0x00c5, /* 5 Sweden */ 0x00dc, 0x00e9, 0x00e4, 0x00f6, 0x00e5, 0x00fc}, { 0x0023, 0x0024, 0x0040, 0x00ba, 0x005c, 0x00e9, /* 6 Italy */ 0x005e, 0x00f9, 0x00e0, 0x00f2, 0x00e8, 0x00ec}, { 0x20a7, 0x0024, 0x0040, 0x00a1, 0x00d1, 0x00bf, /* 7 Spain I */ 0x005e, 0x0060, 0x00a8, 0x00f1, 0x007d, 0x007e}, { 0x0023, 0x0024, 0x0040, 0x005b, 0x00a5, 0x005d, /* 8 Japan (Eng) */ 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, { 0x0023, 0x00a4, 0x00c9, 0x00c6, 0x00d8, 0x00c5, /* 9 Norway */ 0x00dc, 0x00e9, 0x00e6, 0x00f8, 0x00e5, 0x00fc}, { 0x0023, 0x0024, 0x00c9, 0x00c6, 0x00d8, 0x00c5, /* 10 Denmark II */ 0x00dc, 0x00e9, 0x00e6, 0x00f8, 0x00e5, 0x00fc}, { 0x0023, 0x0024, 0x00e1, 0x00a1, 0x00d1, 0x00bf, /* 11 Spain II */ 0x00e9, 0x0060, 0x00ed, 0x00f1, 0x00f3, 0x00fa}, { 0x0023, 0x0024, 0x00e1, 0x00a1, 0x00d1, 0x00bf, /* 12 Lat America */ 0x00e9, 0x00fc, 0x00ed, 0x00f1, 0x00f3, 0x00fa}, { 0x0023, 0x0024, 0x0040, 0x005b, 0x20a9, 0x005d, /* 13 Korea */ 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, { 0x0023, 0x0024, 0x00a7, 0x00ba, 0x2019, 0x201d, /* 64 Legal */ 0x00b6, 0x0060, 0x00a9, 0x00ae, 0x2020, 0x2122} }; #ifdef ENABLE_ESCP_LOG int escp_do_log = ENABLE_ESCP_LOG; static void escp_log(const char *fmt, ...) { va_list ap; if (escp_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define escp_log(fmt, ...) #endif /* Dump the current page into a formatted file. */ static void dump_page(escp_t *dev) { char path[1024]; strcpy(path, dev->pagepath); strcat(path, dev->page_fn); png_write_rgb(path, dev->page->pixels, dev->page->w, dev->page->h, dev->page->pitch, dev->palcol); } static void new_page(escp_t *dev, int8_t save, int8_t resetx) { /* Dump the current page if needed. */ if (save && dev->page) dump_page(dev); if (resetx) dev->curr_x = dev->left_margin; /* Clear page. */ dev->curr_y = dev->top_margin; if (dev->page) { dev->page->dirty = 0; memset(dev->page->pixels, 0x00, (size_t) dev->page->pitch * dev->page->h); } /* Make the page's file name. */ plat_tempfile(dev->page_fn, NULL, ".png"); } static void pulse_timer(void *priv) { escp_t *dev = (escp_t *) priv; if (dev->ack) { dev->ack = 0; lpt_irq(dev->lpt, 1); } timer_disable(&dev->pulse_timer); } static void timeout_timer(void *priv) { escp_t *dev = (escp_t *) priv; if (dev->page->dirty) new_page(dev, 1, 1); timer_stop(&dev->timeout_timer); } static void fill_palette(uint8_t redmax, uint8_t greenmax, uint8_t bluemax, uint8_t colorID, escp_t *dev) { uint8_t colormask; double red = (double) redmax / (double) 30.9; double green = (double) greenmax / (double) 30.9; double blue = (double) bluemax / (double) 30.9; colormask = colorID <<= 5; for (uint8_t i = 0; i < 32; i++) { dev->palcol[i + colormask].r = 255 - (uint8_t) floor(red * (double) i); dev->palcol[i + colormask].g = 255 - (uint8_t) floor(green * (double) i); dev->palcol[i + colormask].b = 255 - (uint8_t) floor(blue * (double) i); } } static void update_font(escp_t *dev) { char path[1024]; const char *fn; FT_Matrix matrix; double hpoints = 10.5; double vpoints = 10.5; /* We need the FreeType library. */ if (ft_lib == NULL) return; /* Release current font if we have one. */ if (dev->fontface) FT_Done_Face(dev->fontface); if (dev->print_quality == QUALITY_DRAFT) { if (dev->font_style & STYLE_ITALICS) fn = FONT_FILE_DOTMATRIX_ITALIC; else fn = FONT_FILE_DOTMATRIX; } else switch (dev->lq_typeface) { case TYPEFACE_ROMAN: fn = (dev->font_style & STYLE_PROP) ? FONT_FILE_ROMAN : FONT_FILE_COURIER; break; case TYPEFACE_SANSSERIF: fn = FONT_FILE_SANSSERIF; break; case TYPEFACE_COURIER: fn = FONT_FILE_COURIER; break; case TYPEFACE_SCRIPT: fn = FONT_FILE_SCRIPT; break; case TYPEFACE_OCRA: fn = FONT_FILE_OCRA; break; case TYPEFACE_OCRB: fn = FONT_FILE_OCRB; break; default: fn = FONT_FILE_ROMAN; } /* Create a full pathname for the ROM file. */ strcpy(path, dev->fontpath); path_slash(path); strcat(path, fn); escp_log("Temp file=%s\n", path); /* Load the new font. */ if (FT_New_Face(ft_lib, path, 0, &dev->fontface)) { escp_log("ESC/P: unable to load font '%s'\n", path); dev->fontface = NULL; } if (!dev->multipoint_mode) { dev->actual_cpi = dev->cpi; if (!(dev->font_style & STYLE_CONDENSED)) { hpoints *= 10.0 / dev->cpi; vpoints *= 10.0 / dev->cpi; } if (!(dev->font_style & STYLE_PROP)) { if ((dev->cpi == 10.0) && (dev->font_style & STYLE_CONDENSED)) { dev->actual_cpi = 17.14; hpoints *= 10.0 / 17.14; } if ((dev->cpi == 12) && (dev->font_style & STYLE_CONDENSED)) { dev->actual_cpi = 20.0; hpoints *= 10.0 / 20.0; vpoints *= 10.0 / 12.0; } } else if (dev->font_style & STYLE_CONDENSED) hpoints /= 2.0; if ((dev->font_style & STYLE_DOUBLEWIDTH) || (dev->font_style & STYLE_DOUBLEWIDTHONELINE)) { dev->actual_cpi /= 2.0; hpoints *= 2.0; } if (dev->font_style & STYLE_DOUBLEHEIGHT) vpoints *= 2.0; } else { /* Multipoint mode. */ dev->actual_cpi = dev->multipoint_cpi; hpoints = vpoints = dev->multipoint_size; } if ((dev->font_style & STYLE_SUPERSCRIPT) || (dev->font_style & STYLE_SUBSCRIPT)) { hpoints *= 2.0 / 3.0; vpoints *= 2.0 / 3.0; dev->actual_cpi /= 2.0 / 3.0; } FT_Set_Char_Size(dev->fontface, (uint16_t) (hpoints * 64), (uint16_t) (vpoints * 64), dev->dpi, dev->dpi); if ((dev->print_quality != QUALITY_DRAFT) && ((dev->font_style & STYLE_ITALICS) || (dev->char_tables[dev->curr_char_table] == 0))) { /* Italics transformation. */ matrix.xx = 0x10000L; matrix.xy = (FT_Fixed) (0.20 * 0x10000L); matrix.yx = 0; matrix.yy = 0x10000L; FT_Set_Transform(dev->fontface, &matrix, 0); } } /* Select a ASCII->Unicode mapping by CP number */ static void init_codepage(escp_t *dev, uint16_t num) { /* Get the codepage map for this number. */ select_codepage(num, dev->curr_cpmap); } static void reset_printer(escp_t *dev) { dev->top_margin = dev->left_margin = 0.0; dev->right_margin = dev->page_width; switch (dev->paper_size) { case PAPER_A4: dev->page_height = A4_PAGE_HEIGHT; break; case PAPER_LEGAL_SIDE: dev->page_height = LEGAL_PAGE_WIDTH; break; case PAPER_B4_SIDE: dev->page_height = B4_PAGE_WIDTH; break; case PAPER_LETTER: default: dev->page_height = LETTER_PAGE_HEIGHT; } dev->bottom_margin = dev->page_height; /* TODO: these should be configurable. */ dev->color = COLOR_BLACK; dev->curr_x = dev->curr_y = 0.0; dev->esc_seen = 0; dev->fss_seen = 0; dev->esc_pending = 0; dev->esc_parms_req = dev->esc_parms_curr = 0; dev->lpi = PAGE_LPI; dev->linespacing = 1.0 / dev->lpi; dev->cpi = PAGE_CPI; dev->curr_char_table = 1; dev->font_style = 0; dev->print_quality = QUALITY_DRAFT; dev->extra_intra_space = 0.0; dev->print_upper_control = 1; dev->bg_remaining_bytes = 0; dev->density_k = 0; dev->density_l = 1; dev->density_y = 2; dev->density_z = 3; dev->char_tables[0] = 0; /* italics */ dev->char_tables[1] = dev->char_tables[2] = dev->char_tables[3] = 437; /* all other tables use CP437 */ dev->defined_unit = -1.0; dev->multipoint_mode = 0; dev->multipoint_size = 0.0; dev->multipoint_cpi = 0.0; dev->hmi = -1; dev->msb = 255; dev->print_everything_count = 0; dev->lq_typeface = TYPEFACE_COURIER; init_codepage(dev, dev->char_tables[dev->curr_char_table]); update_font(dev); new_page(dev, 0, 1); for (uint8_t i = 0; i < 32; i++) dev->horizontal_tabs[i] = i * 8.0 * (1.0 / dev->cpi); dev->num_horizontal_tabs = 32; dev->num_vertical_tabs = -1; if (dev->page != NULL) dev->page->dirty = 0; escp_log("ESC/P: width=%.1fin,height=%.1fin dpi=%i cpi=%i lpi=%i\n", dev->page_width, dev->page_height, (int) dev->dpi, (int) dev->cpi, (int) dev->lpi); } static void reset_printer_hard(escp_t *dev) { dev->ack = 0; timer_disable(&dev->pulse_timer); timer_stop(&dev->timeout_timer); reset_printer(dev); } static void setup_bit_image(escp_t *dev, uint8_t density, uint16_t num_columns) { escp_log("Density=%d\n", density); switch (density) { case 0: dev->bg_h_density = 60; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 1: dev->bg_h_density = 120; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 2: dev->bg_h_density = 120; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 0; dev->bg_bytes_per_column = 1; break; case 3: dev->bg_h_density = 240; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 0; dev->bg_bytes_per_column = 1; break; case 4: dev->bg_h_density = 80; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 5: if (dev->lang >= LANG_ESCP) break; dev->bg_h_density = 72; dev->bg_v_density = 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 6: dev->bg_h_density = 90; dev->bg_v_density = dev->lang >= LANG_ESCP ? 60 : 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 7: if (dev->lang >= LANG_ESCP) break; dev->bg_h_density = 144; dev->bg_v_density = 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 1; break; case 32: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 60; dev->bg_v_density = 180; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 3; break; case 33: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 120; dev->bg_v_density = 180; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 3; break; case 38: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 90; dev->bg_v_density = 180; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 3; break; case 39: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 180; dev->bg_v_density = 180; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 3; break; case 40: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 360; dev->bg_v_density = 180; dev->bg_adjacent = 0; dev->bg_bytes_per_column = 3; break; case 71: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 180; dev->bg_v_density = 360; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 6; break; case 72: if (dev->lang < LANG_ESCP) break; dev->bg_h_density = 360; dev->bg_v_density = 360; dev->bg_adjacent = 0; dev->bg_bytes_per_column = 6; break; case 73: if (dev->lang < LANG_ESCP2) break; dev->bg_h_density = 360; dev->bg_v_density = 360; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 6; break; case 254: if (dev->lang >= LANG_ESCP) break; dev->bg_h_density = 120; dev->bg_v_density = 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 2; break; case 255: if (dev->lang >= LANG_ESCP) break; dev->bg_h_density = 60; dev->bg_v_density = 72; dev->bg_adjacent = 1; dev->bg_bytes_per_column = 2; break; default: escp_log("ESC/P: Unsupported bit image density %d.\n", density); break; } dev->bg_remaining_bytes = num_columns * dev->bg_bytes_per_column; dev->bg_bytes_read = 0; } /* This is the actual ESC/P interpreter. */ static int process_char(escp_t *dev, uint8_t ch) { double new_x; double new_y; double move_to; double unit_size; double reverse; double new_top; double new_bottom; uint16_t rel_move; int16_t i; escp_log("Esc_seen=%d, fss_seen=%d\n", dev->esc_seen, dev->fss_seen); /* Determine number of additional command params that are expected. */ if (dev->esc_seen || dev->fss_seen) { dev->esc_pending = ch; if (dev->fss_seen) dev->esc_pending |= 0x800; dev->esc_seen = dev->fss_seen = 0; dev->esc_parms_curr = 0; escp_log("Command pending=%02x, font path=%s\n", dev->esc_pending, dev->fontpath); switch (dev->esc_pending) { case 0x02: // Undocumented case 0x0e: // Select double-width printing (one line) (ESC SO) case 0x0f: // Select condensed printing (ESC SI) case '#': // Cancel MSB control case '0': // Select 1/8-inch line spacing case '2': // Select 1/6-inch line spacing case '4': // Select italic font case '5': // Cancel italic font case '6': // Enable printing of upper control codes case '7': // Enable upper control codes case '<': // Unidirectional mode (one line) case '=': // Set MSB to 0 case '>': // Set MSB to 1 case '@': // Initialize printer case 'E': // Select bold font case 'F': // Cancel bold font case 'G': // Select double-strike printing case 'H': // Cancel double-strike printing case 'M': // Select 10.5-point, 12-cpi case 'O': // Cancel bottom margin case 'P': // Select 10.5-point, 10-cpi case 'T': // Cancel superscript/subscript printing dev->esc_parms_req = 0; break; case 'g': // Select 10.5-point, 15-cpi dev->esc_parms_req = 0; if (dev->lang == LANG_EX1000) { dev->esc_pending = 0; return 1; } break; case '1': // Select 7/72-inch line spacing case '8': // Disable paper-out detector case '9': // Enable paper-out detector dev->esc_parms_req = 0; if (dev->lang >= LANG_ESCP) { dev->esc_pending = 0; return 1; } break; case 0x0a: // Reverse line feed (IBM's ESC LF) case 0x0c: // Return to top of current page (IBM's ESC FF) case 0x834: // Select italic font (FS 4) (= ESC 4) case 0x835: // Cancel italic font (FS 5) (= ESC 5) case 0x846: // Select forward feed mode (FS F) case 0x852: // Select reverse feed mode (FS R) dev->esc_parms_req = 0; if (dev->lang < LANG_ESCP2) { dev->esc_pending = 0; return 1; } break; case 'h': // Select double or quadruple size (IBM's) case '~': // Select/Deselect slash zero (IBM's?) case 0x832: // Select 1/6-inch line spacing (FS 2) (= ESC 2) case 0x833: // Set n/360-inch line spacing (FS 3) (= ESC +) case 0x841: // Set n/60-inch line spacing (FS A) (= ESC A) case 0x843: // Select LQ type style (FS C) (= ESC k) case 0x845: // Select character width (FS E) case 0x849: // Select character table (FS I) (= ESC t) case 0x853: // Select High Speed/High Density elite pitch (FS S) case 0x856: // Turn double-height printing on/off (FS V) (= ESC w) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } case '+': // Set n/360-inch line spacing if (dev->lang < LANG_ESCP) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } case 'w': // Turn double-height printing on/off if (dev->lang == LANG_EX1000) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } case 0x19: // Control paper loading/ejecting (ESC EM) case ' ': // Set intercharacter space case '!': // Master select case '-': // Turn underline on/off case '/': // Select vertical tab channel case '3': // Set n/180-inch line spacing case 'A': // Set n/60-inch line spacing case 'C': // Set page length in lines case 'I': // Select character type and print pitch case 'J': // Advance print position vertically case 'N': // Set bottom margin case 'Q': // Set right margin case 'R': // Select an international character set case 'S': // Select superscript/subscript printing case 'U': // Turn unidirectional mode on/off case 'W': // Turn double-width printing on/off case 'a': // Select justification case 'k': // Select typeface case 'l': // Set left margin case 'p': // Turn proportional mode on/off case 'r': // Select printing color case 's': // Select low-speed mode case 't': // Select character table case 'x': // Select LQ or draft dev->esc_parms_req = 1; break; case 'f': // Absolute horizontal tab in columns if (dev->lang != LANG_9PIN) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } dev->esc_parms_req = 1; break; case 'i': // Immediate print case 'j': // Reverse paper feed if (dev->lang != LANG_EX1000) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } dev->esc_parms_req = 1; break; case 'c': // Set horizontal motion index (HMI) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } case '$': // Set absolute horizontal print position case '?': // Reassign bit-image mode case 'K': // Select 60-dpi graphics case 'L': // Select 120-dpi graphics case 'Y': // Select 120-dpi, double-speed graphics case 'Z': // Select 240-dpi graphics case '\\': // Set relative horizontal print position case 0x85a: // Print 24-bit hex-density graphics (FS Z) dev->esc_parms_req = 2; break; case 'e': // Set vertical tab stops every n lines if (dev->lang != LANG_9PIN) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } dev->esc_parms_req = 2; break; case 'X': // Select font by pitch and point if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } case '^': /* 9-pin ESC/P: Select 60/120-dpi, 9-bit graphics IBM: Enable printing of all character codes on next character */ if (dev->lang <= LANG_9PIN) dev->esc_parms_req = 3; else { dev->esc_parms_req = 0; if (dev->lang == LANG_ESCP) dev->esc_pending = 0; } break; case '*': // Select bit image dev->esc_parms_req = 3; break; case '[': // Select character height, width, line spacing (IBM's) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } dev->esc_parms_req = 7; break; case 'b': // Set vertical tabs in VFU channels case 'B': // Set vertical tabs dev->num_vertical_tabs = 0; return 1; case 'D': // Set horizontal tabs dev->num_horizontal_tabs = 0; return 1; case '%': // Select user-defined set case '&': // Define user-defined characters case ':': // Copy ROM to RAM escp_log("ESC/P: User-defined characters not supported (0x%02x).\n", dev->esc_pending); return 1; case '(': // Two bytes sequence if (dev->lang == LANG_EX1000) { dev->esc_parms_req = 0; dev->esc_pending = 0; } /* return and wait for second ESC byte */ return 1; case '.': if (dev->lang >= LANG_ESCP2) { fatal("ESC/P: Print Raster Graphics (2E) command is not implemented.\nTerminating the emulator to avoid endless PNG generation.\n"); exit(-1); } dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; default: escp_log("ESC/P: Unknown command ESC %c (0x%02x). Unable to skip parameters.\n", dev->esc_pending >= 0x20 ? dev->esc_pending : '?', dev->esc_pending); dev->esc_parms_req = 0; dev->esc_pending = 0; return 1; } if (dev->esc_parms_req > 0) { /* return and wait for parameters to appear */ return 1; } } /* parameter checking for the 2-byte ESC/P2 commands */ if (dev->esc_pending == '(') { dev->esc_pending = 0x0200 + ch; escp_log("Two-byte command pending=%03x, font path=%s\n", dev->esc_pending, dev->fontpath); switch (dev->esc_pending) { case 0x025e: // Print data as characters (ESC (^) if (dev->lang < LANG_ESCP2) default: /* ESC ( commands are always followed by a "number of parameters" word parameter */ dev->esc_pending = 0x101; /* dummy value to be checked later */ case 0x0242: // Bar code setup and print (ESC (B) dev->esc_parms_req = 2; break; case 0x0255: // Set unit (ESC (U) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 2; dev->esc_pending = 0x101; break; } dev->esc_parms_req = 3; break; case 0x0243: // Set page length in defined unit (ESC (C) case 0x0256: // Set absolute vertical print position (ESC (V) case 0x0276: // Set relative vertical print position (ESC (v) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 2; dev->esc_pending = 0x101; break; } dev->esc_parms_req = 4; break; case 0x022d: // Select line/score (ESC (-) if (dev->lang == LANG_9PIN) { dev->esc_parms_req = 2; dev->esc_pending = 0x101; break; } case 0x0228: // Assign character table (ESC (t) dev->esc_parms_req = 5; break; case 0x0263: // Set page format (ESC (c) if (dev->lang < LANG_ESCP2) { dev->esc_parms_req = 2; dev->esc_pending = 0x101; break; } dev->esc_parms_req = 6; break; } // Wait for more parameters. return 1; } /* Ignore VFU channel setting. */ if (dev->esc_pending == 'b') { dev->esc_pending = 'B'; return 1; } /* Collect vertical tabs. */ if (dev->esc_pending == 'B') { /* check if we're done */ if ((ch == 0) || (dev->num_vertical_tabs > 0 && dev->vertical_tabs[dev->num_vertical_tabs - 1] > (double) ch * dev->linespacing)) { dev->esc_pending = 0; } else { if (dev->num_vertical_tabs >= 0 && dev->num_vertical_tabs < 16) dev->vertical_tabs[dev->num_vertical_tabs++] = (double) ch * dev->linespacing; } } /* Collect horizontal tabs. */ if (dev->esc_pending == 'D') { /* check if we're done... */ if ((ch == 0) || (dev->num_horizontal_tabs > 0 && dev->horizontal_tabs[dev->num_horizontal_tabs - 1] > (double) ch * (1.0 / dev->cpi))) { dev->esc_pending = 0; } else { if (dev->num_horizontal_tabs < 32) dev->horizontal_tabs[dev->num_horizontal_tabs++] = (double) ch * (1.0 / dev->cpi); } } /* Check if we're still collecting parameters for the current command. */ if (dev->esc_parms_curr < dev->esc_parms_req) { /* store current parameter */ dev->esc_parms[dev->esc_parms_curr++] = ch; /* do we still need to continue collecting parameters? */ if (dev->esc_parms_curr < dev->esc_parms_req) return 1; } /* Handle the pending ESC command. */ if (dev->esc_pending != 0) { switch (dev->esc_pending) { case 0x02: /* undocumented; ignore */ break; case 0x0e: /* select double-width (one line) (ESC SO) */ if (!dev->multipoint_mode) { dev->hmi = -1; dev->font_style |= STYLE_DOUBLEWIDTHONELINE; update_font(dev); } break; case 0x0f: /* select condensed printing (ESC SI) */ if (!dev->multipoint_mode && (dev->cpi != 15.0)) { dev->hmi = -1; dev->font_style |= STYLE_CONDENSED; update_font(dev); } break; case 0x19: /* control paper loading/ejecting (ESC EM) */ /* We are not really loading paper, so most * commands can be ignored */ if (dev->esc_parms[0] == 'R') new_page(dev, 1, 0); break; case 0x20: /* set intercharacter space (ESC SP) */ if (!dev->multipoint_mode) { dev->extra_intra_space = (double) dev->esc_parms[0] / (dev->print_quality == QUALITY_DRAFT ? 120.0 : 180.0); dev->hmi = -1; update_font(dev); } break; case '!': /* master select */ dev->cpi = (dev->esc_parms[0]) & 0x01 ? 12.0 : 10.0; /* Reset first seven bits. */ dev->font_style &= 0xFF80; if (dev->esc_parms[0] & 0x02) dev->font_style |= STYLE_PROP; if (dev->esc_parms[0] & 0x04) dev->font_style |= STYLE_CONDENSED; if (dev->esc_parms[0] & 0x08) dev->font_style |= STYLE_BOLD; if (dev->esc_parms[0] & 0x10) dev->font_style |= STYLE_DOUBLESTRIKE; if (dev->esc_parms[0] & 0x20) dev->font_style |= STYLE_DOUBLEWIDTH; if (dev->esc_parms[0] & 0x40) dev->font_style |= STYLE_ITALICS; if (dev->esc_parms[0] & 0x80) { dev->font_score = SCORE_SINGLE; dev->font_style |= STYLE_UNDERLINE; } dev->hmi = -1; dev->multipoint_mode = 0; update_font(dev); break; case '#': /* cancel MSB control */ dev->msb = 255; break; case '$': /* set abs horizontal print position */ unit_size = dev->defined_unit; if (unit_size < 0) unit_size = 60.0; new_x = dev->left_margin + ((double) PARAM16(0) / unit_size); if (new_x <= dev->right_margin) dev->curr_x = new_x; break; case 0x85a: /* Print 24-bit hex-density graphics (FS Z) */ setup_bit_image(dev, 40, PARAM16(0)); break; case '*': /* select bit image */ setup_bit_image(dev, dev->esc_parms[0], PARAM16(1)); break; case 0x833: /* Set n/360-inch line spacing (FS 3) */ case '+': /* set n/360-inch line spacing */ dev->linespacing = (double) dev->esc_parms[0] / 360.0; break; case 0x2d: /* turn underline on/off (ESC -) */ if (dev->esc_parms[0] == 0 || dev->esc_parms[0] == '0') dev->font_style &= ~STYLE_UNDERLINE; if (dev->esc_parms[0] == 1 || dev->esc_parms[0] == '1') { dev->font_style |= STYLE_UNDERLINE; dev->font_score = SCORE_SINGLE; } update_font(dev); break; case '/': /* select vertical tab channel */ /* Ignore */ break; case '0': /* select 1/8-inch line spacing */ dev->linespacing = 1.0 / 8.0; break; case '1': /* select 7/72-inch line spacing */ dev->linespacing = 7.0 / 72.0; break; case '2': /* select 1/6-inch line spacing */ dev->linespacing = 1.0 / 6.0; break; case '3': /* set n/180 or n/216-inch line spacing */ dev->linespacing = (double) dev->esc_parms[0] / (dev->lang >= LANG_ESCP ? 180.0 : 216.0); break; case '4': /* select italic font */ dev->font_style |= STYLE_ITALICS; update_font(dev); break; case '5': /* cancel italic font */ dev->font_style &= ~STYLE_ITALICS; update_font(dev); break; case '6': /* enable printing of upper control codes */ dev->print_upper_control = 1; break; case '7': /* enable upper control codes */ dev->print_upper_control = 0; break; case '<': /* unidirectional mode (one line) */ /* We don't have a print head, so just * ignore this. */ break; case '=': /* set MSB to 0 */ dev->msb = 0; break; case '>': /* set MSB to 1 */ dev->msb = 1; break; case '?': /* reassign bit-image mode */ if ((dev->esc_parms[1] == 3 || dev->esc_parms[1] == 5) && dev->lang >= LANG_ESCP) break; if (dev->esc_parms[1] > 7) { if (dev->lang < LANG_ESCP) break; if (dev->esc_parms[1] > 40 && dev->lang < LANG_ESCP2) break; } if (dev->esc_parms[0] == 'K') dev->density_k = dev->esc_parms[1]; if (dev->esc_parms[0] == 'L') dev->density_l = dev->esc_parms[1]; if (dev->esc_parms[0] == 'Y') dev->density_y = dev->esc_parms[1]; if (dev->esc_parms[0] == 'Z') dev->density_z = dev->esc_parms[1]; break; case '@': /* initialize printer */ reset_printer(dev); break; case 'A': /* set n/60 or n/72-inch line spacing */ case 0x841: // FS A dev->linespacing = (double) dev->esc_parms[0] / (dev->lang >= LANG_ESCP ? 60.0 : 72.0); break; case 'C': /* set page length in lines */ if (dev->esc_parms[0] != 0) { dev->page_height = dev->bottom_margin = (double) dev->esc_parms[0] * dev->linespacing; } else { /* == 0 => Set page length in inches */ dev->esc_parms_req = 1; dev->esc_parms_curr = 0; dev->esc_pending = 0x100; /* dummy value for later */ return 1; } break; case 'E': /* select bold font */ dev->font_style |= STYLE_BOLD; update_font(dev); break; case 'F': /* cancel bold font */ dev->font_style &= ~STYLE_BOLD; update_font(dev); break; case 'G': /* select double-strike printing */ dev->font_style |= STYLE_DOUBLESTRIKE; break; case 'H': /* cancel double-strike printing */ dev->font_style &= ~STYLE_DOUBLESTRIKE; break; case 'J': /* advance print pos vertically */ dev->curr_y += ((double) dev->esc_parms[0] / (dev->lang >= LANG_ESCP ? 180.0 : 216.0)); if (dev->curr_y > dev->bottom_margin) new_page(dev, 1, 0); break; case 'K': /* select 60-dpi graphics */ setup_bit_image(dev, dev->density_k, PARAM16(0)); break; case 'L': /* select 120-dpi graphics */ setup_bit_image(dev, dev->density_l, PARAM16(0)); break; case 'M': /* select 10.5-point, 12-cpi */ dev->cpi = 12.0; dev->hmi = -1; dev->multipoint_mode = 0; update_font(dev); break; case 'N': /* set bottom margin */ dev->top_margin = 0.0; dev->bottom_margin = (double) dev->esc_parms[0] * dev->linespacing; break; case 'O': /* cancel bottom (and top) margin */ dev->top_margin = 0.0; dev->bottom_margin = dev->page_height; break; case 'P': /* select 10.5-point, 10-cpi */ dev->cpi = 10.0; dev->hmi = -1; dev->multipoint_mode = 0; update_font(dev); break; case 'Q': /* set right margin */ dev->right_margin = ((double) dev->esc_parms[0] - 1.0) / dev->cpi; break; case 'R': /* select an intl character set */ if ((dev->esc_parms[0] <= 13) || (dev->esc_parms[0] == 64)) { if (dev->esc_parms[0] == 64) dev->esc_parms[0] = 14; dev->curr_cpmap[0x23] = intCharSets[dev->esc_parms[0]][0]; dev->curr_cpmap[0x24] = intCharSets[dev->esc_parms[0]][1]; dev->curr_cpmap[0x40] = intCharSets[dev->esc_parms[0]][2]; dev->curr_cpmap[0x5b] = intCharSets[dev->esc_parms[0]][3]; dev->curr_cpmap[0x5c] = intCharSets[dev->esc_parms[0]][4]; dev->curr_cpmap[0x5d] = intCharSets[dev->esc_parms[0]][5]; dev->curr_cpmap[0x5e] = intCharSets[dev->esc_parms[0]][6]; dev->curr_cpmap[0x60] = intCharSets[dev->esc_parms[0]][7]; dev->curr_cpmap[0x7b] = intCharSets[dev->esc_parms[0]][8]; dev->curr_cpmap[0x7c] = intCharSets[dev->esc_parms[0]][9]; dev->curr_cpmap[0x7d] = intCharSets[dev->esc_parms[0]][10]; dev->curr_cpmap[0x7e] = intCharSets[dev->esc_parms[0]][11]; } break; case 'S': /* select superscript/subscript printing */ if ((dev->esc_parms[0] == 0) || (dev->esc_parms[0] == '0')) dev->font_style |= STYLE_SUBSCRIPT; if ((dev->esc_parms[0] == 1) || (dev->esc_parms[1] == '1')) dev->font_style |= STYLE_SUPERSCRIPT; update_font(dev); break; case 'T': /* cancel superscript/subscript printing */ dev->font_style &= 0xFFFF - STYLE_SUPERSCRIPT - STYLE_SUBSCRIPT; update_font(dev); break; case 'U': /* turn unidirectional mode on/off */ /* We don't have a print head, so just ignore this. */ break; case 'W': /* turn double-width printing on/off */ if (!dev->multipoint_mode) { dev->hmi = -1; if ((dev->esc_parms[0] == 0) || (dev->esc_parms[0] == '0')) dev->font_style &= ~STYLE_DOUBLEWIDTH; if ((dev->esc_parms[0] == 1) || (dev->esc_parms[0] == '1')) dev->font_style |= STYLE_DOUBLEWIDTH; update_font(dev); } break; case 'X': /* select font by pitch and point */ dev->multipoint_mode = 1; /* Copy currently non-multipoint CPI if no value was set so far. */ if (dev->multipoint_cpi == 0.0) { dev->multipoint_cpi = dev->cpi; } if (dev->esc_parms[0] > 0) { /* set CPI */ if (dev->esc_parms[0] == 1) { /* Proportional spacing. */ dev->font_style |= STYLE_PROP; } else if (dev->esc_parms[0] >= 5) dev->multipoint_cpi = 360.0 / (double) dev->esc_parms[0]; } if (dev->multipoint_size == 0.0) dev->multipoint_size = 10.5; if (PARAM16(1) > 0) { /* set points */ dev->multipoint_size = ((double) PARAM16(1)) / 2.0; } update_font(dev); break; case 'Y': /* select 120-dpi, double-speed graphics */ setup_bit_image(dev, dev->density_y, PARAM16(0)); break; case 'Z': /* select 240-dpi graphics */ setup_bit_image(dev, dev->density_z, PARAM16(0)); break; case '\\': /* set relative horizontal print pos */ rel_move = PARAM16(0); unit_size = dev->defined_unit; if (unit_size < 0) unit_size = (dev->print_quality == QUALITY_DRAFT || dev->lang < LANG_ESCP) ? 120.0 : 180.0; if (dev->curr_x + ((double) rel_move / unit_size) < dev->right_margin) dev->curr_x += ((double) rel_move / unit_size); break; case '^': // Select 60/120-dpi, 9-pin graphics) setup_bit_image(dev, 255 - dev->esc_parms[0], PARAM16(0)); break; case 'a': /* select justification */ /* Ignore. */ break; case 'c': /* set horizontal motion index (HMI) */ dev->hmi = (double) PARAM16(0) / 360.0; dev->extra_intra_space = 0.0; break; case 'g': /* select 10.5-point, 15-cpi */ dev->cpi = 15; dev->hmi = -1; dev->multipoint_mode = 0; update_font(dev); break; case 0x846: // Select forward feed mode (FS F) - set reverse not implemented yet if (dev->linespacing < 0) dev->linespacing *= -1; break; case 'j': // Reverse paper feed (ESC j) reverse = dev->curr_y - (double) PARAM16(0) / (double) 216.0; if (reverse < dev->left_margin) dev->curr_y = dev->left_margin; else dev->curr_y = reverse; break; case 'k': /* select typeface */ if ((dev->esc_parms[0] <= 11) || (dev->esc_parms[0] == 30) || (dev->esc_parms[0] == 31)) dev->lq_typeface = dev->esc_parms[0]; update_font(dev); break; case 'l': /* set left margin */ dev->left_margin = ((double) dev->esc_parms[0] - 1.0) / dev->cpi; if (dev->curr_x < dev->left_margin) dev->curr_x = dev->left_margin; break; case 'p': /* Turn proportional mode on/off */ if ((dev->esc_parms[0] == 0) || (dev->esc_parms[0] == '0')) dev->font_style &= ~STYLE_PROP; if ((dev->esc_parms[0] == 1) || (dev->esc_parms[0] == '1')) { dev->font_style |= STYLE_PROP; dev->print_quality = QUALITY_LQ; } dev->multipoint_mode = 0; dev->hmi = -1; update_font(dev); break; case 'r': /* select printing color */ if (dev->esc_parms[0] == 0 || dev->esc_parms[0] > 6) dev->color = COLOR_BLACK; else dev->color = dev->esc_parms[0] << 5; break; case 's': /* select low-speed mode */ /* Ignore. */ break; case 't': /* select character table */ case 0x849: /* Select character table (FS I) */ if (dev->esc_parms[0] < 4) { dev->curr_char_table = dev->esc_parms[0]; } else if ((dev->esc_parms[0] >= '0') && (dev->esc_parms[0] <= '3')) { dev->curr_char_table = dev->esc_parms[0] - '0'; } init_codepage(dev, dev->char_tables[dev->curr_char_table]); update_font(dev); break; case 'w': /* turn double-height printing on/off */ if (!dev->multipoint_mode) { if ((dev->esc_parms[0] == 0) || (dev->esc_parms[0] == '0')) dev->font_style &= ~STYLE_DOUBLEHEIGHT; if ((dev->esc_parms[0] == 1) || (dev->esc_parms[0] == '1')) dev->font_style |= STYLE_DOUBLEHEIGHT; update_font(dev); } break; case 'x': /* select LQ or draft */ if ((dev->esc_parms[0] == 0) || (dev->esc_parms[0] == '0')) { dev->print_quality = QUALITY_DRAFT; dev->font_style |= STYLE_CONDENSED; } if ((dev->esc_parms[0] == 1) || (dev->esc_parms[0] == '1')) { dev->print_quality = QUALITY_LQ; dev->font_style &= ~STYLE_CONDENSED; } dev->hmi = -1; update_font(dev); break; /* Our special command markers. */ case 0x0100: /* set page length in inches (ESC C NUL) */ dev->page_height = (double) dev->esc_parms[0]; dev->bottom_margin = dev->page_height; dev->top_margin = 0.0; break; case 0x0101: /* skip unsupported ESC ( command */ dev->esc_parms_req = PARAM16(0); dev->esc_parms_curr = 0; break; /* Extended ESC ( commands */ case 0x0228: /* assign character table (ESC (t) */ case 0x0274: if (dev->esc_parms[2] < 4 && dev->esc_parms[3] < 16) { dev->char_tables[dev->esc_parms[2]] = codepages[dev->esc_parms[3]]; if (dev->esc_parms[2] == dev->curr_char_table) init_codepage(dev, dev->char_tables[dev->curr_char_table]); } break; case 0x022d: /* select line/score (ESC (-) */ dev->font_style &= ~(STYLE_UNDERLINE | STYLE_STRIKETHROUGH | STYLE_OVERSCORE); dev->font_score = dev->esc_parms[4]; if (dev->font_score) { if (dev->esc_parms[3] == 1) dev->font_style |= STYLE_UNDERLINE; if (dev->esc_parms[3] == 2) dev->font_style |= STYLE_STRIKETHROUGH; if (dev->esc_parms[3] == 3) dev->font_style |= STYLE_OVERSCORE; } update_font(dev); break; case 0x0242: /* bar code setup and print (ESC (B) */ // ERRLOG("ESC/P: Barcode printing not supported.\n"); /* Find out how many bytes to skip. */ dev->esc_parms_req = PARAM16(0); dev->esc_parms_curr = 0; break; case 0x0243: /* set page length in defined unit (ESC (C) */ if (dev->esc_parms[0] && (dev->defined_unit > 0)) { dev->page_height = dev->bottom_margin = (double) PARAM16(2) * dev->defined_unit; dev->top_margin = 0.0; } break; case 0x0255: /* set unit (ESC (U) */ dev->defined_unit = 3600.0 / (double) dev->esc_parms[2]; break; case 0x0256: /* set abse vertical print pos (ESC (V) */ unit_size = dev->defined_unit; if (unit_size < 0) unit_size = 360.0; new_y = dev->top_margin + (double) PARAM16(2) * unit_size; if (new_y > dev->bottom_margin) new_page(dev, 1, 0); else dev->curr_y = new_y; break; case 0x025e: /* print data as characters (ESC (^) */ dev->print_everything_count = PARAM16(0); break; case 0x0263: /* set page format (ESC (c) */ if (dev->defined_unit > 0.0) { new_top = (double) PARAM16(2) * dev->defined_unit; new_bottom = (double) PARAM16(4) * dev->defined_unit; if (new_top >= new_bottom) break; if (new_top < dev->page_height) dev->top_margin = new_top; if (new_bottom < dev->page_height) dev->bottom_margin = new_bottom; if (dev->top_margin > dev->curr_y) dev->curr_y = dev->top_margin; } break; case 0x0276: /* set relative vertical print pos (ESC (v) */ { unit_size = dev->defined_unit; if (unit_size < 0.0) unit_size = 360.0; new_y = dev->curr_y + (double) ((int16_t) PARAM16(2)) * unit_size; if (new_y > dev->top_margin) { if (new_y > dev->bottom_margin) new_page(dev, 1, 0); else dev->curr_y = new_y; } } break; default: break; } dev->esc_pending = 0; return 1; } escp_log("CH=%02x\n", ch); /* Now handle the "regular" control characters. */ switch (ch) { case 0x00: return 1; case 0x07: /* Beeper (BEL) */ /* TODO: beep? */ return 1; case 0x08: /* Backspace (BS) */ new_x = dev->curr_x - (1.0 / dev->actual_cpi); if (dev->hmi > 0) new_x = dev->curr_x - dev->hmi; if (new_x >= dev->left_margin) dev->curr_x = new_x; return 1; case 0x09: /* Tab horizontally (HT) */ /* Find tab right to current pos. */ move_to = -1.0; for (i = 0; i < dev->num_horizontal_tabs; i++) { if (dev->horizontal_tabs[i] > dev->curr_x) move_to = dev->horizontal_tabs[i]; } /* Nothing found or out of page bounds => Ignore. */ if (move_to > 0.0 && move_to < dev->right_margin) dev->curr_x = move_to; return 1; case 0x0b: /* Tab vertically (VT) */ if (dev->num_vertical_tabs == 0) { /* All tabs cleared? => Act like CR */ dev->curr_x = dev->left_margin; } else if (dev->num_vertical_tabs < 0) { /* No tabs set since reset => Act like LF */ dev->curr_x = dev->left_margin; dev->curr_y += dev->linespacing; if (dev->curr_y > dev->bottom_margin) new_page(dev, 1, 0); } else { /* Find tab below current pos. */ move_to = -1; for (i = 0; i < dev->num_vertical_tabs; i++) { if (dev->vertical_tabs[i] > dev->curr_y) move_to = dev->vertical_tabs[i]; } /* Nothing found => Act like FF. */ if (move_to > dev->bottom_margin || move_to < 0) new_page(dev, 1, 0); else dev->curr_y = move_to; } if (dev->font_style & STYLE_DOUBLEWIDTHONELINE) { dev->font_style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE; update_font(dev); } return 1; case 0x0c: /* Form feed (FF) */ if (dev->font_style & STYLE_DOUBLEWIDTHONELINE) { dev->font_style &= ~STYLE_DOUBLEWIDTHONELINE; update_font(dev); } new_page(dev, 1, 1); return 1; case 0x0d: /* Carriage Return (CR) */ dev->curr_x = dev->left_margin; if (!dev->autofeed) return 1; fallthrough; case 0x0a: /* Line feed */ if (dev->font_style & STYLE_DOUBLEWIDTHONELINE) { dev->font_style &= ~STYLE_DOUBLEWIDTHONELINE; update_font(dev); } dev->curr_x = dev->left_margin; dev->curr_y += dev->linespacing; if ((dev->curr_y + 0.0001f) > dev->bottom_margin) new_page(dev, 1, 0); return 1; case 0x0e: /* select Real64-width printing (one line) (SO) */ if (!dev->multipoint_mode) { dev->hmi = -1; dev->font_style |= STYLE_DOUBLEWIDTHONELINE; update_font(dev); } return 1; case 0x0f: /* select condensed printing (SI) */ if (!dev->multipoint_mode) { dev->hmi = -1; dev->font_style |= STYLE_CONDENSED; update_font(dev); } return 1; case 0x11: /* select printer (DC1) */ /* Ignore. */ return 0; case 0x12: /* cancel condensed printing (DC2) */ dev->hmi = -1; dev->font_style &= ~STYLE_CONDENSED; update_font(dev); return 1; case 0x13: /* deselect printer (DC3) */ /* Ignore. */ return 1; case 0x14: /* cancel double-width printing (one line) (DC4) */ dev->hmi = -1; dev->font_style &= ~STYLE_DOUBLEWIDTHONELINE; update_font(dev); return 1; case 0x18: /* cancel line (CAN) */ return 1; case 0x1b: /* ESC */ dev->esc_seen = 1; return 1; case 0x1c: /* FS (IBM Proprinter II) TODO: Make an IBM printer. */ if (dev->lang == LANG_ESCP2) { dev->fss_seen = 1; return 1; } default: /* This is a printable character -> print it. */ return 0; } } /* TODO: This can be optimized quite a bit... I'm just too lazy right now ;-) */ static void blit_glyph(escp_t *dev, unsigned destx, unsigned desty, int8_t add) { const FT_Bitmap *bitmap = &dev->fontface->glyph->bitmap; uint8_t src; uint8_t *dst; /* check if freetype is available */ if (ft_lib == NULL) return; for (unsigned int y = 0; y < bitmap->rows; y++) { for (unsigned int x = 0; x < bitmap->width; x++) { src = *(bitmap->buffer + x + y * bitmap->pitch); /* ignore background, and respect page size */ if (src > 0 && (destx + x < (unsigned) dev->page->w) && (desty + y < (unsigned) dev->page->h)) { dst = (uint8_t *) dev->page->pixels + (x + destx) + (y + desty) * dev->page->pitch; src >>= 3; if (add) { if (((*dst) & 0x1f) + src > 31) *dst |= (dev->color | 0x1f); else { *dst += src; *dst |= dev->color; } } else *dst = src | dev->color; } } } } /* Draw anti-aliased line. */ static void draw_hline(escp_t *dev, unsigned from_x, unsigned to_x, unsigned y, int8_t broken) { unsigned breakmod = dev->dpi / 15; unsigned gapstart = (breakmod * 4) / 5; for (unsigned int x = from_x; x <= to_x; x++) { /* Skip parts if broken line or going over the border. */ if ((!broken || (x % breakmod <= gapstart)) && (x < dev->page->w)) { if (y > 0 && (y - 1) < dev->page->h) *((uint8_t *) dev->page->pixels + x + (y - 1) * (unsigned) dev->page->pitch) = 240; if (y < dev->page->h) *((uint8_t *) dev->page->pixels + x + y * (unsigned) dev->page->pitch) = !broken ? 255 : 240; if (y + 1 < dev->page->h) *((uint8_t *) dev->page->pixels + x + (y + 1) * (unsigned) dev->page->pitch) = 240; } } } static void print_bit_graph(escp_t *dev, uint8_t ch) { uint8_t pixel_w; /* width of the "pixel" */ uint8_t pixel_h; /* height of the "pixel" */ double old_y; dev->bg_column[dev->bg_bytes_read++] = ch; dev->bg_remaining_bytes--; /* Only print after reading a full column. */ if (dev->bg_bytes_read < dev->bg_bytes_per_column) return; old_y = dev->curr_y; pixel_w = 1; pixel_h = 1; if (dev->bg_adjacent) { /* if page DPI is bigger than bitgraphics DPI, drawn pixels get "bigger" */ pixel_w = dev->dpi / dev->bg_h_density > 0 ? dev->dpi / dev->bg_h_density : 1; pixel_h = dev->dpi / dev->bg_v_density > 0 ? dev->dpi / dev->bg_v_density : 1; } for (uint8_t i = 0; i < dev->bg_bytes_per_column; i++) { /* for each byte */ for (uint8_t j = 128; j != 0; j >>= 1) { if (dev->bg_v_density == 72 && i == 1 && j != 128) // 9-bit mode from ESC ^ break; /* for each bit */ if (dev->bg_column[i] & j) { /* draw a "pixel" */ for (uint8_t xx = 0; xx < pixel_w; xx++) { for (uint8_t yy = 0; yy < pixel_h; yy++) { if (((PIXX + xx) < (unsigned) dev->page->w) && ((PIXY + yy) < (unsigned) dev->page->h)) *((uint8_t *) dev->page->pixels + (PIXX + xx) + (PIXY + yy) * dev->page->pitch) |= (dev->color | 0x1f); } } } dev->curr_y += 1.0 / (double) dev->bg_v_density; } } /* Mark page dirty. */ dev->page->dirty = 1; /* Restore Y-position. */ dev->curr_y = old_y; dev->bg_bytes_read = 0; /* Advance print head. */ dev->curr_x += 1.0 / dev->bg_h_density; } static void handle_char(escp_t *dev, uint8_t ch) { FT_UInt char_index; uint16_t pen_x; uint16_t pen_y; uint16_t line_start; uint16_t line_y; double x_advance; if (dev->page == NULL) return; /* MSB mode */ if (dev->msb != 255) { if (dev->msb == 0) ch &= 0x7f; else if (dev->msb == 1) ch |= 0x80; } if (dev->bg_remaining_bytes > 0) { print_bit_graph(dev, ch); return; } /* "print everything" mode? aka. ESC ( ^ */ if (dev->print_everything_count > 0) { escp_log("Print everything count=%d\n", dev->print_everything_count); /* do not process command char, just continue */ dev->print_everything_count--; } else if (process_char(dev, ch)) { /* command was processed */ return; } /* We cannot print if we have no font loaded. */ if (dev->fontface == NULL) return; if (ch == 0x01) ch = 0x20; /* ok, so we need to print the character now */ if (ft_lib) { char_index = FT_Get_Char_Index(dev->fontface, dev->curr_cpmap[ch]); FT_Load_Glyph(dev->fontface, char_index, FT_LOAD_DEFAULT); FT_Render_Glyph(dev->fontface->glyph, FT_RENDER_MODE_NORMAL); } pen_x = PIXX + fmax(0.0, dev->fontface->glyph->bitmap_left); pen_y = (uint16_t) (PIXY + fmax(0.0, -dev->fontface->glyph->bitmap_top + dev->fontface->size->metrics.ascender / 64)); if (dev->font_style & STYLE_SUBSCRIPT) pen_y += dev->fontface->glyph->bitmap.rows / 2; /* mark the page as dirty if anything is drawn */ if ((ch != 0x20) || (dev->font_score != SCORE_NONE)) dev->page->dirty = 1; /* draw the glyph */ blit_glyph(dev, pen_x, pen_y, 0); blit_glyph(dev, pen_x + 1, pen_y, 1); /* doublestrike -> draw glyph a second time, 1px below */ if (dev->font_style & STYLE_DOUBLESTRIKE) { blit_glyph(dev, pen_x, pen_y + 1, 1); blit_glyph(dev, pen_x + 1, pen_y + 1, 1); } /* bold -> draw glyph a second time, 1px to the right */ if (dev->font_style & STYLE_BOLD) { blit_glyph(dev, pen_x + 1, pen_y, 1); blit_glyph(dev, pen_x + 2, pen_y, 1); blit_glyph(dev, pen_x + 3, pen_y, 1); } line_start = PIXX; if (dev->font_style & STYLE_PROP) x_advance = dev->fontface->glyph->advance.x / (dev->dpi * 64.0); else { if (dev->hmi < 0) x_advance = 1.0 / dev->actual_cpi; else x_advance = dev->hmi; } x_advance += dev->extra_intra_space; dev->curr_x += x_advance; /* Line printing (underline etc.) */ if (dev->font_score != SCORE_NONE && (dev->font_style & (STYLE_UNDERLINE | STYLE_STRIKETHROUGH | STYLE_OVERSCORE))) { /* Find out where to put the line. */ line_y = PIXY; if (dev->font_style & STYLE_UNDERLINE) line_y = (PIXY + (uint16_t) (dev->fontface->size->metrics.height * 0.9)); if (dev->font_style & STYLE_STRIKETHROUGH) line_y = (PIXY + (uint16_t) (dev->fontface->size->metrics.height * 0.45)); if (dev->font_style & STYLE_OVERSCORE) line_y = PIXY - ((dev->font_score == SCORE_DOUBLE || dev->font_score == SCORE_DOUBLEBROKEN) ? 5 : 0); draw_hline(dev, pen_x, PIXX, line_y, dev->font_score == SCORE_SINGLEBROKEN || dev->font_score == SCORE_DOUBLEBROKEN); if (dev->font_score == SCORE_DOUBLE || dev->font_score == SCORE_DOUBLEBROKEN) draw_hline(dev, line_start, PIXX, line_y + 5, dev->font_score == SCORE_SINGLEBROKEN || dev->font_score == SCORE_DOUBLEBROKEN); } if ((dev->curr_x + x_advance) > dev->right_margin) { dev->curr_x = dev->left_margin; dev->curr_y += dev->linespacing; if (dev->curr_y > dev->bottom_margin) new_page(dev, 1, 0); } } static void write_data(uint8_t val, void *priv) { escp_t *dev = (escp_t *) priv; if (dev == NULL) return; dev->data = val; } static void strobe(uint8_t old, uint8_t val, void *priv) { escp_t *dev = (escp_t *) priv; if (dev == NULL) return; /* Data is strobed to the parallel printer on the falling edge of the strobe bit. */ if (!(val & 0x01) && (old & 0x01)) { /* Process incoming character. */ handle_char(dev, dev->data); if (timer_is_on(&dev->timeout_timer)) { timer_stop(&dev->timeout_timer); #ifdef USE_DYNAREC if (cpu_use_dynarec) update_tsc(); #endif } /* ACK it, will be read on next READ STATUS. */ dev->ack = 1; timer_set_delay_u64(&dev->pulse_timer, ISACONST); timer_on_auto(&dev->timeout_timer, 5000000.0); } } static void write_ctrl(uint8_t val, void *priv) { escp_t *dev = (escp_t *) priv; if (dev == NULL) return; if (val & 0x08) { /* SELECT */ /* select printer */ dev->select = 1; } if ((val & 0x04) && !(dev->ctrl & 0x04)) { /* reset printer */ dev->select = 0; reset_printer_hard(dev); } /* Data is strobed to the parallel printer on the falling edge of the strobe bit. */ if (!(val & 0x01) && (dev->ctrl & 0x01)) { /* Process incoming character. */ handle_char(dev, dev->data); if (timer_is_on(&dev->timeout_timer)) { timer_stop(&dev->timeout_timer); #ifdef USE_DYNAREC if (cpu_use_dynarec) update_tsc(); #endif } /* ACK it, will be read on next READ STATUS. */ dev->ack = 1; timer_set_delay_u64(&dev->pulse_timer, ISACONST); timer_on_auto(&dev->timeout_timer, 5000000.0); } dev->ctrl = val; dev->autofeed = ((val & 0x02) > 0); } static uint8_t read_ctrl(void *priv) { const escp_t *dev = (escp_t *) priv; return 0xe0 | (dev->autofeed ? 0x02 : 0x00) | (dev->ctrl & 0xfd); } static uint8_t read_status(void *priv) { const escp_t *dev = (escp_t *) priv; uint8_t ret = 0x1f; ret |= 0x80; if (!dev->ack) ret |= 0x40; return ret; } static void * escp_init(const device_t *info) { escp_t *dev = NULL; /* Initialize FreeType. */ if (ft_lib == NULL) { if (FT_Init_FreeType(&ft_lib)) { pclog("ESC/P: FT_Init_FreeType failed\n"); ft_lib = NULL; return (NULL); } } /* Initialize a device instance. */ dev = (escp_t *) calloc(1, sizeof(escp_t)); dev->ctrl = 0x04; dev->lpt = lpt_attach(write_data, write_ctrl, strobe, read_status, read_ctrl, NULL, NULL, dev); dev->lang = device_get_config_int("language"); rom_get_full_path(dev->fontpath, "roms/printer/fonts/"); /* Create a full pathname for the font files. */ if (strlen(dev->fontpath) == 0) { ui_msgbox_header(MBX_ERROR, plat_get_string(STRING_ESCP_ERROR_TITLE), plat_get_string(STRING_ESCP_ERROR_DESC)); free(dev); return (NULL); } /* Create the full path for the page images. */ path_append_filename(dev->pagepath, usr_path, "printer"); if (!plat_dir_check(dev->pagepath)) plat_dir_create(dev->pagepath); path_slash(dev->pagepath); dev->paper_size = device_get_config_int("paper_size"); switch (dev->paper_size) { case PAPER_A4: dev->page_width = A4_PAGE_WIDTH; dev->page_height = A4_PAGE_HEIGHT; break; case PAPER_LEGAL_SIDE: dev->page_height = LEGAL_PAGE_WIDTH; dev->page_width = LEGAL_PAGE_HEIGHT; break; case PAPER_B4_SIDE: dev->page_height = B4_PAGE_WIDTH; dev->page_width = B4_PAGE_HEIGHT; break; case PAPER_LETTER: default: dev->page_width = LETTER_PAGE_WIDTH; dev->page_height = LETTER_PAGE_HEIGHT; } dev->dpi = dev->lang >= LANG_ESCP ? 360 : 240; /* Create 8-bit grayscale buffer for the page. */ dev->page = (psurface_t *) malloc(sizeof(psurface_t)); dev->page->w = (int) (dev->dpi * dev->page_width); dev->page->h = (int) (dev->dpi * dev->page_height); dev->page->pitch = dev->page->w; dev->page->pixels = (uint8_t *) malloc((size_t) dev->page->pitch * dev->page->h); memset(dev->page->pixels, 0x00, (size_t) dev->page->pitch * dev->page->h); /* Initialize parameters. */ for (uint8_t i = 0; i < 32; i++) { dev->palcol[i].r = 255; dev->palcol[i].g = 255; dev->palcol[i].b = 255; } /* 0 = all white needed for logic 000 */ fill_palette(0, 0, 0, 1, dev); /* 1 = magenta* 001 */ fill_palette(0, 255, 0, 1, dev); /* 2 = cyan* 010 */ fill_palette(255, 0, 0, 2, dev); /* 3 = "violet" 011 */ fill_palette(255, 255, 0, 3, dev); /* 4 = yellow* 100 */ fill_palette(0, 0, 255, 4, dev); /* 5 = red 101 */ fill_palette(0, 255, 255, 5, dev); /* 6 = green 110 */ fill_palette(255, 0, 255, 6, dev); /* 7 = black 111 */ fill_palette(255, 255, 255, 7, dev); dev->color = COLOR_BLACK; dev->fontface = 0; dev->autofeed = 0; reset_printer(dev); escp_log("ESC/P: created a virtual page of dimensions %d x %d pixels.\n", dev->page->w, dev->page->h); timer_add(&dev->pulse_timer, pulse_timer, dev, 0); timer_add(&dev->timeout_timer, timeout_timer, dev, 0); return dev; } static void escp_close(void *priv) { escp_t *dev = (escp_t *) priv; if (dev == NULL) return; if (dev->page != NULL) { /* Print last page if it contains data. */ if (dev->page->dirty) dump_page(dev); if (dev->page->pixels != NULL) free(dev->page->pixels); free(dev->page); } FT_Done_Face(dev->fontface); free(dev); } // clang-format off #if 0 static const device_config_t lpt_prt_escp_config[] = { { .name = "", .description = "", .type = CONFIG_END } }; #endif static const device_config_t lpt_prt_escp_config[] = { { .name = "language", .description = "Language", .type = CONFIG_SELECTION, .default_string = NULL, .default_int = LANG_ESCP2, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "EX-1000", .value = LANG_EX1000 }, #if 0 { .description = "9-pin", .value = LANG_9PIN }, { .description = "ESC/P", .value = LANG_ESCP }, #endif { .description = "ESC/P 2", .value = LANG_ESCP2 }, { .description = "" } }, .bios = { { 0 } } }, { .name = "paper_size", .description = "Paper Size", .type = CONFIG_SELECTION, .default_string = NULL, .default_int = 0, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "Letter", .value = PAPER_LETTER }, { .description = "A4", .value = PAPER_A4 }, { .description = "Legal (sideways)", .value = PAPER_LEGAL_SIDE }, { .description = "B4 (sideways)", .value = PAPER_B4_SIDE }, { .description = "" } }, .bios = { { 0 } } }, { .name = "", .description = "", .type = CONFIG_END } }; // clang-format on const device_t lpt_prt_escp_device = { .name = "Generic ESC/P 2 Dot-Matrix Printer", .internal_name = "dot_matrix", .flags = DEVICE_LPT, .local = 0, .init = escp_init, .close = escp_close, .reset = NULL, .available = NULL, .speed_changed = NULL, .force_redraw = NULL, .config = lpt_prt_escp_config };