diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index 975133f6c6..26f5d5cc82 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -47,7 +47,9 @@ enum { STRING_HW_NOT_AVAILABLE_MACHINE, /* "Machine \"%hs\" is not available..." */ STRING_HW_NOT_AVAILABLE_VIDEO, /* "Video card \"%hs\" is not available..." */ STRING_HW_NOT_AVAILABLE_VIDEO2, /* "Video card #2 \"%hs\" is not available..." */ - STRING_MONITOR_SLEEP /* "Monitor in sleep mode" */ + STRING_MONITOR_SLEEP, /* "Monitor in sleep mode" */ + STRING_GHOSTPCL_ERROR_TITLE, /* "Unable to initialize GhostPCL" */ + STRING_GHOSTPCL_ERROR_DESC /* "gpcl6dll32.dll/gpcl6dll64.dll/libgpcl6 is required..." */ }; /* The Win32 API uses _wcsicmp. */ diff --git a/src/include/86box/prt_devs.h b/src/include/86box/prt_devs.h index 3d9d6673a4..02920e9b0b 100644 --- a/src/include/86box/prt_devs.h +++ b/src/include/86box/prt_devs.h @@ -4,5 +4,6 @@ extern const lpt_device_t lpt_prt_text_device; extern const lpt_device_t lpt_prt_escp_device; extern const lpt_device_t lpt_prt_ps_device; +extern const lpt_device_t lpt_prt_pcl_device; #endif /*EMU_PRT_DEVS_H*/ diff --git a/src/lpt.c b/src/lpt.c index 419e5ad3d5..d0647c0e60 100644 --- a/src/lpt.c +++ b/src/lpt.c @@ -42,6 +42,7 @@ static const struct { {"text_prt", &lpt_prt_text_device }, {"dot_matrix", &lpt_prt_escp_device }, {"postscript", &lpt_prt_ps_device }, + {"pcl", &lpt_prt_pcl_device }, {"plip", &lpt_plip_device }, {"dongle_savquest", &lpt_hasp_savquest_device }, {"", NULL } diff --git a/src/printer/prt_ps.c b/src/printer/prt_ps.c index e6362019f4..3e6612ce1c 100644 --- a/src/printer/prt_ps.c +++ b/src/printer/prt_ps.c @@ -6,13 +6,16 @@ * * This file is part of the 86Box distribution. * - * Implementation of a generic PostScript printer. + * Implementation of a generic PostScript printer and a + * generic PCL 5e printer. * * * * Authors: David Hrdlička, + * Cacodemon345 * * Copyright 2019 David Hrdlička. + * Copyright 2024 Cacodemon345. */ #include @@ -44,15 +47,20 @@ #ifdef _WIN32 # if (!(defined __amd64__ || defined _M_X64 || defined __aarch64__ || defined _M_ARM64)) # define PATH_GHOSTSCRIPT_DLL "gsdll32.dll" +# define PATH_GHOSTPCL_DLL "gpcl6dll32.dll" # else # define PATH_GHOSTSCRIPT_DLL "gsdll64.dll" +# define PATH_GHOSTPCL_DLL "gpcl6dll64.dll" # endif #elif defined __APPLE__ # define PATH_GHOSTSCRIPT_DLL "libgs.dylib" +# define PATH_GHOSTPCL_DLL "libgpcl6.dylib" #else # define PATH_GHOSTSCRIPT_DLL "libgs.so.9" # define PATH_GHOSTSCRIPT_DLL_ALT1 "libgs.so.10" # define PATH_GHOSTSCRIPT_DLL_ALT2 "libgs.so" +# define PATH_GHOSTPCL_DLL "libgpcl6.so.10" +# define PATH_GHOSTPCL_DLL_ALT "libgpcl6.so" #endif #define POSTSCRIPT_BUFFER_LENGTH 65536 @@ -72,6 +80,8 @@ typedef struct ps_t { bool int_pending; bool error; bool autofeed; + bool pcl; + bool pcl_escape; uint8_t ctrl; char printer_path[260]; @@ -141,28 +151,32 @@ pulse_timer(void *priv) static int convert_to_pdf(ps_t *dev) { - volatile int code; + volatile int code, arg = 0; void *instance = NULL; char input_fn[1024]; char output_fn[1024]; - char *gsargv[9]; + char *gsargv[11]; strcpy(input_fn, dev->printer_path); path_slash(input_fn); strcat(input_fn, dev->filename); strcpy(output_fn, input_fn); - strcpy(output_fn + strlen(output_fn) - 3, ".pdf"); - - gsargv[0] = ""; - gsargv[1] = "-dNOPAUSE"; - gsargv[2] = "-dBATCH"; - gsargv[3] = "-dSAFER"; - gsargv[4] = "-sDEVICE=pdfwrite"; - gsargv[5] = "-q"; - gsargv[6] = "-o"; - gsargv[7] = output_fn; - gsargv[8] = input_fn; + strcpy(output_fn + strlen(output_fn) - (dev->pcl ? 4 : 3), ".pdf"); + + gsargv[arg++] = ""; + gsargv[arg++] = "-dNOPAUSE"; + gsargv[arg++] = "-dBATCH"; + gsargv[arg++] = "-dSAFER"; + gsargv[arg++] = "-sDEVICE=pdfwrite"; + if (dev->pcl) { + gsargv[arg++] = "-LPCL"; + gsargv[arg++] = "-lPCL5E"; + } + gsargv[arg++] = "-q"; + gsargv[arg++] = "-o"; + gsargv[arg++] = output_fn; + gsargv[arg++] = input_fn; code = gsapi_new_instance(&instance, dev); if (code < 0) @@ -171,7 +185,7 @@ convert_to_pdf(ps_t *dev) code = gsapi_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8); if (code == 0) - code = gsapi_init_with_args(instance, 9, gsargv); + code = gsapi_init_with_args(instance, arg, gsargv); if (code == 0 || code == gs_error_Quit) code = gsapi_exit(instance); @@ -198,19 +212,22 @@ write_buffer(ps_t *dev, bool finish) return; if (dev->filename[0] == 0) - plat_tempfile(dev->filename, NULL, ".ps"); + plat_tempfile(dev->filename, NULL, dev->pcl ? ".pcl" : ".ps"); strcpy(path, dev->printer_path); path_slash(path); strcat(path, dev->filename); - fp = plat_fopen(path, "a"); + fp = plat_fopen(path, dev->pcl ? "ab" : "a"); if (fp == NULL) return; fseek(fp, 0, SEEK_END); - fprintf(fp, "%.*s", POSTSCRIPT_BUFFER_LENGTH, dev->buffer); + if (dev->pcl) + fwrite(dev->buffer, 1, dev->buffer_pos, fp); + else + fprintf(fp, "%.*s", POSTSCRIPT_BUFFER_LENGTH, dev->buffer); fclose(fp); @@ -249,8 +266,25 @@ ps_write_data(uint8_t val, void *priv) static void process_data(ps_t *dev) { - /* Check for non-printable characters */ - if ((dev->data < 0x20) || (dev->data == 0x7f)) { + /* On PCL, check for escape sequences. */ + if (dev->pcl) { + if (dev->data == 0x1B) + dev->pcl_escape = true; + else if (dev->pcl_escape) { + dev->pcl_escape = false; + if (dev->data == 0xE) { + dev->buffer[dev->buffer_pos++] = dev->data; + dev->buffer[dev->buffer_pos] = 0; + + if (dev->buffer_pos > 2) + write_buffer(dev, true); + + return; + } + } + } + /* On PostScript, check for non-printable characters. */ + else if ((dev->data < 0x20) || (dev->data == 0x7f)) { switch (dev->data) { /* The following characters are considered white-space by the PostScript specification */ @@ -376,6 +410,51 @@ ps_init(void *lpt) return dev; } +static void * +pcl_init(void *lpt) +{ + ps_t *dev; + gsapi_revision_t rev; + + dev = (ps_t *) malloc(sizeof(ps_t)); + memset(dev, 0x00, sizeof(ps_t)); + dev->ctrl = 0x04; + dev->lpt = lpt; + dev->pcl = true; + + /* Try loading the DLL. */ + ghostscript_handle = dynld_module(PATH_GHOSTPCL_DLL, ghostscript_imports); +#ifdef PATH_GHOSTPCL_DLL_ALT + if (ghostscript_handle == NULL) { + ghostscript_handle = dynld_module(PATH_GHOSTPCL_DLL_ALT, ghostscript_imports); + } +#endif + if (ghostscript_handle == NULL) { + ui_msgbox_header(MBX_ERROR, plat_get_string(STRING_GHOSTPCL_ERROR_TITLE), plat_get_string(STRING_GHOSTPCL_ERROR_DESC)); + } else { + if (gsapi_revision(&rev, sizeof(rev)) == 0) { + pclog("Loaded %s, rev %ld (%ld)\n", rev.product, rev.revision, rev.revisiondate); + } else { + dynld_close(ghostscript_handle); + ghostscript_handle = NULL; + } + } + + /* Cache print folder path. */ + memset(dev->printer_path, 0x00, sizeof(dev->printer_path)); + path_append_filename(dev->printer_path, usr_path, "printer"); + if (!plat_dir_check(dev->printer_path)) + plat_dir_create(dev->printer_path); + path_slash(dev->printer_path); + + timer_add(&dev->pulse_timer, pulse_timer, dev, 0); + timer_add(&dev->timeout_timer, timeout_timer, dev, 0); + + reset_ps(dev); + + return dev; +} + static void ps_close(void *priv) { @@ -406,3 +485,15 @@ const lpt_device_t lpt_prt_ps_device = { .read_status = ps_read_status, .read_ctrl = NULL }; + +const lpt_device_t lpt_prt_pcl_device = { + .name = "Generic PCL5e Printer", + .internal_name = "pcl", + .init = pcl_init, + .close = ps_close, + .write_data = ps_write_data, + .write_ctrl = ps_write_ctrl, + .read_data = NULL, + .read_status = ps_read_status, + .read_ctrl = NULL +}; diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index edfefa39f9..a200e38ec9 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -585,14 +585,17 @@ c16stombs(char dst[], const uint16_t src[], int len) #ifdef _WIN32 # if defined(__amd64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) -# define LIB_NAME_GS "gsdll64.dll" +# define LIB_NAME_GS "gsdll64.dll" +# define LIB_NAME_GPCL "gpcl6dll64.dll" # else -# define LIB_NAME_GS "gsdll32.dll" +# define LIB_NAME_GS "gsdll32.dll" +# define LIB_NAME_GPCL "gpcl6dll32.dll" # endif # define LIB_NAME_PCAP "Npcap" # define MOUSE_CAPTURE_KEYSEQ "F8+F12" #else # define LIB_NAME_GS "libgs" +# define LIB_NAME_GPCL "libgpcl6" # define LIB_NAME_PCAP "libpcap" # define MOUSE_CAPTURE_KEYSEQ "Ctrl+End" #endif @@ -613,6 +616,8 @@ ProgSettings::reloadStrings() translatedstrings[STRING_PCAP_ERROR_DESC] = QCoreApplication::translate("", "Make sure %1 is installed and that you are on a %1-compatible network connection.").arg(LIB_NAME_PCAP).toStdWString(); translatedstrings[STRING_GHOSTSCRIPT_ERROR_TITLE] = QCoreApplication::translate("", "Unable to initialize Ghostscript").toStdWString(); translatedstrings[STRING_GHOSTSCRIPT_ERROR_DESC] = QCoreApplication::translate("", "%1 is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files.").arg(LIB_NAME_GS).toStdWString(); + translatedstrings[STRING_GHOSTPCL_ERROR_TITLE] = QCoreApplication::translate("", "Unable to initialize GhostPCL").toStdWString(); + translatedstrings[STRING_GHOSTPCL_ERROR_DESC] = QCoreApplication::translate("", "%1 is required for automatic conversion of PCL files to PDF.\n\nAny documents sent to the generic PCL printer will be saved as Printer Command Language (.pcl) files.").arg(LIB_NAME_GPCL).toStdWString(); translatedstrings[STRING_HW_NOT_AVAILABLE_MACHINE] = QCoreApplication::translate("", "Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine.").toStdWString(); translatedstrings[STRING_HW_NOT_AVAILABLE_VIDEO] = QCoreApplication::translate("", "Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card.").toStdWString(); translatedstrings[STRING_HW_NOT_AVAILABLE_VIDEO2] = QCoreApplication::translate("", "Video card #2 \"%hs\" is not available due to missing ROMs in the roms/video directory. Disabling the second video card.").toStdWString(); diff --git a/src/unix/unix.c b/src/unix/unix.c index 58cb1448f2..e08b82133e 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -266,6 +266,10 @@ plat_get_string(int i) return L"Make sure libpcap is installed and that you are on a libpcap-compatible network connection."; case STRING_GHOSTSCRIPT_ERROR_TITLE: return L"Unable to initialize Ghostscript"; + case STRING_GHOSTPCL_ERROR_TITLE: + return L"Unable to initialize GhostPCL"; + case STRING_GHOSTPCL_ERROR_DESC: + return L"libgpcl6 is required for automatic conversion of PCL files to PDF.\n\nAny documents sent to the generic PCL printer will be saved as Printer Command Language (.pcl) files."; case STRING_HW_NOT_AVAILABLE_MACHINE: return L"Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine."; case STRING_HW_NOT_AVAILABLE_VIDEO: