Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/output-pcl.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/output-pcl.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1550 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +/* Lifted from ghostscript gdevjlm.h */ +/* + * The notion that there is such a thing as a "PCL printer" is a fiction: no + * two "PCL" printers, even at the same PCL level, have identical command + * sets. (The H-P documentation isn't fully accurate either; for example, + * it doesn't reveal that the DeskJet printers implement anything beyond PCL + * 3.) + * + * This file contains feature definitions for a generic monochrome PCL + * driver (gdevdljm.c), and the specific feature values for all such + * printers that Ghostscript currently supports. + */ + +/* Printer spacing capabilities. Include at most one of these. */ +#define PCL_NO_SPACING 0 /* no vertical spacing capability, must be 0 */ +#define PCL3_SPACING 1 /* <ESC>*p+<n>Y (PCL 3) */ +#define PCL4_SPACING 2 /* <ESC>*b<n>Y (PCL 4) */ +#define PCL5_SPACING 4 /* <ESC>*b<n>Y and clear seed row (PCL 5) */ +/* The following is only used internally. */ +#define PCL_ANY_SPACING \ + (PCL3_SPACING | PCL4_SPACING | PCL5_SPACING) + +/* Individual printer properties. Any subset of these may be included. */ +#define PCL_MODE_2_COMPRESSION 8 /* compression mode 2 supported */ + /* (PCL 4) */ +#define PCL_MODE_3_COMPRESSION 16 /* compression modes 2 & 3 supported */ + /* (PCL 5) */ +#define PCL_END_GRAPHICS_DOES_RESET 32 /* <esc>*rB resets all parameters */ +#define PCL_HAS_DUPLEX 64 /* <esc>&l<duplex>S supported */ +#define PCL_CAN_SET_PAPER_SIZE 128 /* <esc>&l<sizecode>A supported */ +#define PCL_CAN_PRINT_COPIES 256 /* <esc>&l<copies>X supported */ +#define HACK__IS_A_LJET4PJL 512 +#define HACK__IS_A_OCE9050 1024 +#define PCL_HAS_ORIENTATION 2048 +#define PCL_CAN_SET_CUSTOM_PAPER_SIZE 4096 +#define PCL_HAS_RICOH_PAPER_SIZES 8192 + +/* Shorthands for the most common spacing/compression combinations. */ +#define PCL_MODE0 PCL3_SPACING +#define PCL_MODE0NS PCL_NO_SPACING +#define PCL_MODE2 (PCL4_SPACING | PCL_MODE_2_COMPRESSION) +#define PCL_MODE2P (PCL_NO_SPACING | PCL_MODE_2_COMPRESSION) +#define PCL_MODE3 (PCL5_SPACING | PCL_MODE_3_COMPRESSION) +#define PCL_MODE3NS (PCL_NO_SPACING | PCL_MODE_3_COMPRESSION) + +#define MIN_SKIP_LINES 7 +static const char *const from2to3 = "\033*b3M"; +static const char *const from3to2 = "\033*b2M"; +static const int penalty_from2to3 = 5; /* strlen(from2to3); */ +static const int penalty_from3to2 = 5; /* strlen(from3to2); */ + +/* Generic */ +static const fz_pcl_options fz_pcl_options_generic = +{ + (PCL_MODE2 | PCL_END_GRAPHICS_DOES_RESET | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_SET_CUSTOM_PAPER_SIZE), + "\033&k1W\033*b2M", + "\033&k1W\033*b2M" +}; + +/* H-P DeskJet */ +static const fz_pcl_options fz_pcl_options_ljet4 = +{ + (PCL_MODE2 | PCL_END_GRAPHICS_DOES_RESET | PCL_CAN_SET_PAPER_SIZE), + "\033&k1W\033*b2M", + "\033&k1W\033*b2M" +}; + +/* H-P DeskJet 500 */ +static const fz_pcl_options fz_pcl_options_dj500 = +{ + (PCL_MODE3 | PCL_END_GRAPHICS_DOES_RESET | PCL_CAN_SET_PAPER_SIZE), + "\033&k1W", + "\033&k1W" +}; + +/* Kyocera FS-600 */ +static const fz_pcl_options fz_pcl_options_fs600 = +{ + (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES), + "\033*r0F\033&u%dD", + "\033*r0F\033&u%dD" +}; + +/* H-P original LaserJet */ +/* H-P LaserJet Plus */ +static const fz_pcl_options fz_pcl_options_lj = +{ + (PCL_MODE0), + "\033*b0M", + "\033*b0M" +}; + +/* H-P LaserJet IIp, IId */ +static const fz_pcl_options fz_pcl_options_lj2 = +{ + (PCL_MODE2P | PCL_CAN_SET_PAPER_SIZE), + "\033*r0F\033*b2M", + "\033*r0F\033*b2M" +}; + +/* H-P LaserJet III* */ +static const fz_pcl_options fz_pcl_options_lj3 = +{ + (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES), + "\033&l-180u36Z\033*r0F", + "\033&l-180u36Z\033*r0F" +}; + +/* H-P LaserJet IIId */ +static const fz_pcl_options fz_pcl_options_lj3d = +{ + (PCL_MODE3 | PCL_HAS_DUPLEX | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES), + "\033&l-180u36Z\033*r0F", + "\033&l180u36Z\033*r0F" +}; + +/* H-P LaserJet 4 */ +static const fz_pcl_options fz_pcl_options_lj4 = +{ + (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES), + "\033&l-180u36Z\033*r0F\033&u%dD", + "\033&l-180u36Z\033*r0F\033&u%dD" +}; + +/* H-P LaserJet 4 PL */ +static const fz_pcl_options fz_pcl_options_lj4pl = +{ + (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES | HACK__IS_A_LJET4PJL), + "\033&l-180u36Z\033*r0F\033&u%dD", + "\033&l-180u36Z\033*r0F\033&u%dD" +}; + +/* H-P LaserJet 4d */ +static const fz_pcl_options fz_pcl_options_lj4d = +{ + (PCL_MODE3 | PCL_HAS_DUPLEX | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES), + "\033&l-180u36Z\033*r0F\033&u%dD", + "\033&l180u36Z\033*r0F\033&u%dD" +}; + +/* H-P 2563B line printer */ +static const fz_pcl_options fz_pcl_options_lp2563b = +{ + (PCL_MODE0NS | PCL_CAN_SET_PAPER_SIZE), + "\033*b0M", + "\033*b0M" +}; + +/* OCE 9050 line printer */ +static const fz_pcl_options fz_pcl_options_oce9050 = +{ + (PCL_MODE3NS | PCL_CAN_SET_PAPER_SIZE | HACK__IS_A_OCE9050), + "\033*b0M", + "\033*b0M" +}; + +enum { + eLetterPaper = 0, + eLegalPaper, + eA4Paper, + eExecPaper, + eLedgerPaper, + eA3Paper, + eCOM10Envelope, + eMonarchEnvelope, + eC5Envelope, + eDLEnvelope, + eJB4Paper, + eJB5Paper, + eB5Envelope, + eB5Paper, /* 2.1 */ + eJPostcard, + eJDoublePostcard, + eA5Paper, + eA6Paper, /* 2.0 */ + eJB6Paper, /* 2.0 */ + eJIS8K, /* 2.1 */ + eJIS16K, /* 2.1 */ + eJISExec, /* 2.1 */ + eDefaultPaperSize = 96, /* 2.1 */ + eCustomPaperSize = 101, + eB6JIS = 201, /* non-standard, Ricoh printers */ + eC6Envelope = 202, /* non-standard, Ricoh printers */ + e8Kai = 203, /* non-standard, Ricoh printers */ + e16Kai = 204, /* non-standard, Ricoh printers */ + e12x18 = 205, /* non-standard, Ricoh printers */ + e13x19_2 = 212, /* non-standard, Ricoh printers */ + e13x19 = 213, /* non-standard, Ricoh printers */ + e12_6x19_2 = 214, /* non-standard, Ricoh printers */ + e12_6x18_5 = 215, /* non-standard, Ricoh printers */ + e13x18 = 216, /* non-standard, Ricoh printers */ + eSRA3 = 217, /* non-standard, Ricoh printers */ + eSRA4 = 218, /* non-standard, Ricoh printers */ + e226x310 = 219, /* non-standard, Ricoh printers */ + e310x432 = 220, /* non-standard, Ricoh printers */ + eEngQuatro = 221, /* non-standard, Ricoh printers */ + e11x14 = 222, /* non-standard, Ricoh printers */ + e11x15 = 223, /* non-standard, Ricoh printers */ + e10x14 = 224, /* non-standard, Ricoh printers */ +}; + +static void copy_opts(fz_pcl_options *dst, const fz_pcl_options *src) +{ + if (dst) + *dst = *src; +} + +const char *fz_pcl_write_options_usage = + "PCL output options:\n" + "\tcolorspace=mono: render 1-bit black and white page\n" + "\tcolorspace=rgb: render full color page\n" + "\tpreset=generic|ljet4|dj500|fs600|lj|lj2|lj3|lj3d|lj4|lj4pl|lj4d|lp2563b|oce9050\n" + "\tspacing=0: No vertical spacing capability\n" + "\tspacing=1: PCL 3 spacing (<ESC>*p+<n>Y)\n" + "\tspacing=2: PCL 4 spacing (<ESC>*b<n>Y)\n" + "\tspacing=3: PCL 5 spacing (<ESC>*b<n>Y and clear seed row)\n" + "\tmode2: Enable mode 2 graphics compression\n" + "\tmode3: Enable mode 3 graphics compression\n" + "\teog_reset: End of graphics (<ESC>*rB) resets all parameters\n" + "\thas_duplex: Duplex supported (<ESC>&l<duplex>S)\n" + "\thas_papersize: Papersize setting supported (<ESC>&l<sizecode>A)\n" + "\thas_copies: Number of copies supported (<ESC>&l<copies>X)\n" + "\tis_ljet4pjl: Disable/Enable HP 4PJL model-specific output\n" + "\tis_oce9050: Disable/Enable Oce 9050 model-specific output\n" + "\n"; + +void fz_pcl_preset(fz_context *ctx, fz_pcl_options *opts, const char *preset) +{ + if (preset == NULL || *preset == 0 || !strcmp(preset, "generic")) + copy_opts(opts, &fz_pcl_options_generic); + else if (!strcmp(preset, "ljet4")) + copy_opts(opts, &fz_pcl_options_ljet4); + else if (!strcmp(preset, "dj500")) + copy_opts(opts, &fz_pcl_options_dj500); + else if (!strcmp(preset, "fs600")) + copy_opts(opts, &fz_pcl_options_fs600); + else if (!strcmp(preset, "lj")) + copy_opts(opts, &fz_pcl_options_lj); + else if (!strcmp(preset, "lj2")) + copy_opts(opts, &fz_pcl_options_lj2); + else if (!strcmp(preset, "lj3")) + copy_opts(opts, &fz_pcl_options_lj3); + else if (!strcmp(preset, "lj3d")) + copy_opts(opts, &fz_pcl_options_lj3d); + else if (!strcmp(preset, "lj4")) + copy_opts(opts, &fz_pcl_options_lj4); + else if (!strcmp(preset, "lj4pl")) + copy_opts(opts, &fz_pcl_options_lj4pl); + else if (!strcmp(preset, "lj4d")) + copy_opts(opts, &fz_pcl_options_lj4d); + else if (!strcmp(preset, "lp2563b")) + copy_opts(opts, &fz_pcl_options_lp2563b); + else if (!strcmp(preset, "oce9050")) + copy_opts(opts, &fz_pcl_options_oce9050); + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unknown preset '%s'", preset); +} + +fz_pcl_options * +fz_parse_pcl_options(fz_context *ctx, fz_pcl_options *opts, const char *args) +{ + const char *val; + + memset(opts, 0, sizeof *opts); + + if (fz_has_option(ctx, args, "preset", &val)) + fz_pcl_preset(ctx, opts, val); + else + fz_pcl_preset(ctx, opts, "generic"); + + if (fz_has_option(ctx, args, "spacing", &val)) + { + switch (atoi(val)) + { + case 0: opts->features &= ~PCL_ANY_SPACING; break; + case 1: opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL3_SPACING; break; + case 2: opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL4_SPACING; break; + case 3: opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL5_SPACING; break; + default: fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unsupported PCL spacing %d (0-3 only)", atoi(val)); + } + } + if (fz_has_option(ctx, args, "mode2", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_MODE_2_COMPRESSION; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_MODE_2_COMPRESSION; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for mode2 value"); + } + if (fz_has_option(ctx, args, "mode3", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_MODE_3_COMPRESSION; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_MODE_3_COMPRESSION; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for mode3 value"); + } + if (fz_has_option(ctx, args, "eog_reset", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_END_GRAPHICS_DOES_RESET; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_END_GRAPHICS_DOES_RESET; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for eog_reset value"); + } + if (fz_has_option(ctx, args, "has_duplex", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_HAS_DUPLEX; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_HAS_DUPLEX; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for has_duplex value"); + } + if (fz_has_option(ctx, args, "has_papersize", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_CAN_SET_PAPER_SIZE; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_CAN_SET_PAPER_SIZE; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for has_papersize value"); + } + if (fz_has_option(ctx, args, "has_copies", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~PCL_CAN_PRINT_COPIES; + else if (fz_option_eq(val, "yes")) + opts->features |= PCL_CAN_PRINT_COPIES; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for has_papersize value"); + } + if (fz_has_option(ctx, args, "is_ljet4pjl", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~HACK__IS_A_LJET4PJL; + else if (fz_option_eq(val, "yes")) + opts->features |= HACK__IS_A_LJET4PJL; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for is_ljet4pjl value"); + } + if (fz_has_option(ctx, args, "is_oce9050", &val)) + { + if (fz_option_eq(val, "no")) + opts->features &= ~HACK__IS_A_OCE9050; + else if (fz_option_eq(val, "yes")) + opts->features |= HACK__IS_A_OCE9050; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Expected 'yes' or 'no' for is_oce9050 value"); + } + + return opts; +} + +static void +make_init(fz_pcl_options *pcl, char *buf, unsigned long len, const char *str, int res) +{ + int paper_source = -1; + + fz_snprintf(buf, len, str, res); + + if (pcl->manual_feed_set && pcl->manual_feed) + paper_source = 2; + else if (pcl->media_position_set && pcl->media_position >= 0) + paper_source = pcl->media_position; + if (paper_source >= 0) + { + char buf2[40]; + fz_snprintf(buf2, sizeof(buf2), "\033&l%dH", paper_source); + strncat(buf, buf2, len); + } +} + +static void +pcl_header(fz_context *ctx, fz_output *out, fz_pcl_options *pcl, int num_copies, int xres, int yres, int w, int h) +{ + char odd_page_init[80]; + char even_page_init[80]; + + make_init(pcl, odd_page_init, sizeof(odd_page_init), pcl->odd_page_init, xres); + make_init(pcl, even_page_init, sizeof(even_page_init), pcl->even_page_init, xres); + + if (pcl->page_count == 0) + { + if (pcl->features & HACK__IS_A_LJET4PJL) + fz_write_string(ctx, out, "\033%-12345X@PJL\r\n@PJL ENTER LANGUAGE = PCL\r\n"); + fz_write_string(ctx, out, "\033E"); /* reset printer */ + /* Reset the margins */ + /* ESC & l # E = Top Margin in decipoints */ + fz_write_string(ctx, out, "\033&l0E"); + /* ESC & l # U = Left (Long-Edge) offset registration */ + /* I don't like it that we have to hardcode -180 decipoints in here, but it seems to work. */ + fz_write_string(ctx, out, "\033&l-180U"); + /* ESC & l # Z = Top (Short-Edge) offset registration */ + fz_write_string(ctx, out, "\033&l0Z"); + /* If the printer supports it, set orientation */ + if (pcl->features & PCL_HAS_ORIENTATION) + { + fz_write_printf(ctx, out, "\033&l%dO", pcl->orientation); + } + /* If the printer supports it, set the paper size */ + /* based on the actual requested size. */ + if (pcl->features & PCL_CAN_SET_PAPER_SIZE) + { + /* It probably never hurts to define the page explicitly */ + { + int decipointw = (w * 720 + (xres>>1)) / xres; + int decipointh = (h * 720 + (yres>>1)) / yres; + + fz_write_printf(ctx, out, "\033&f%dI", decipointw); + fz_write_printf(ctx, out, "\033&f%dJ", decipointh); + } + fz_write_printf(ctx, out, "\033&l%dA", pcl->paper_size); + } + /* If printer can duplex, set duplex mode appropriately. */ + if (pcl->features & PCL_HAS_DUPLEX) + { + if (pcl->duplex_set) + { + if (pcl->duplex) + { + if (!pcl->tumble) + fz_write_string(ctx, out, "\033&l1S"); + else + fz_write_string(ctx, out, "\033&l2S"); + } + else + fz_write_string(ctx, out, "\033&l0S"); + } + else + { + /* default to duplex for this printer */ + fz_write_string(ctx, out, "\033&l1S"); + } + } + } + + /* Put out per-page initialization. */ + /* In duplex mode the sheet is already in process, so there are some + * commands which must not be sent to the printer for the 2nd page, + * as these commands will cause the printer to eject the sheet with + * only the 1st page printed. These commands are: + * \033&l%dA (setting paper size) + * \033&l%dH (setting paper tray) + * in simplex mode we set these parameters for each page, + * in duplex mode we set these parameters for each odd page + */ + + if ((pcl->features & PCL_HAS_DUPLEX) && pcl->duplex_set && pcl->duplex) + { + /* We are printing duplex, so change margins as needed */ + if (((pcl->page_count/num_copies)%2) == 0) + { + if (pcl->page_count != 0 && (pcl->features & PCL_CAN_SET_PAPER_SIZE)) + { + fz_write_printf(ctx, out, "\033&l%dA", pcl->paper_size); + } + fz_write_string(ctx, out, "\033&l0o0l0E"); + fz_write_string(ctx, out, pcl->odd_page_init); + } + else + fz_write_string(ctx, out, pcl->even_page_init); + } + else + { + if (pcl->features & PCL_CAN_SET_PAPER_SIZE) + { + fz_write_printf(ctx, out, "\033&l%dA", pcl->paper_size); + } + fz_write_string(ctx, out, "\033&l0o0l0E"); + fz_write_string(ctx, out, pcl->odd_page_init); + } + + fz_write_printf(ctx, out, "\033&l%dX", num_copies); /* # of copies */ + + /* End raster graphics, position cursor at top. */ + fz_write_string(ctx, out, "\033*rB\033*p0x0Y"); + + /* The DeskJet and DeskJet Plus reset everything upon */ + /* receiving \033*rB, so we must reinitialize graphics mode. */ + if (pcl->features & PCL_END_GRAPHICS_DOES_RESET) + { + fz_write_string(ctx, out, pcl->odd_page_init); /* Assume this does the right thing */ + fz_write_printf(ctx, out, "\033&l%dX", num_copies); /* # of copies */ + } + + /* Set resolution. */ + fz_write_printf(ctx, out, "\033*t%dR", xres); + + /* Raster units */ + /* 96,100,120,144,150,160,180,200,225,240,288,300,360,400,450,480,600,720,800,900,1200,1440,1800,2400,3600,7200 */ + /* FIXME: xres vs yres */ + fz_write_printf(ctx, out, "\033&u%dD", xres); + + pcl->page_count++; +} + +typedef struct pcl_papersize_s +{ + int code; + const char *text; + int width; + int height; +} pcl_papersize; + +static const pcl_papersize papersizes[] = +{ + { eLetterPaper, "letter", 2550, 3300}, + { eLegalPaper, "legal", 2550, 4200}, + { eA4Paper, "a4", 2480, 3507}, + { eExecPaper, "executive", 2175, 3150}, + { eLedgerPaper, "ledger", 3300, 5100}, + { eA3Paper, "a3", 3507, 4960}, + { eCOM10Envelope, "com10", 1237, 2850}, + { eMonarchEnvelope, "monarch", 1162, 2250}, + { eC5Envelope, "c5", 1913, 2704}, + { eDLEnvelope, "dl", 1299, 2598}, + { eJB4Paper, "jisb4", 3035, 4299}, + { eJB4Paper, "jis b4", 3035, 4299}, + { eJB5Paper, "jisb5", 2150, 3035}, + { eJB5Paper, "jis b5", 2150, 3035}, + { eB5Envelope, "b5", 2078, 2952}, + { eB5Paper, "b5paper", 2150, 3035}, + { eJPostcard, "jpost", 1181, 1748}, + { eJDoublePostcard, "jpostd", 2362, 1748}, + { eA5Paper, "a5", 1748, 2480}, + { eA6Paper, "a6", 1240, 1748}, + { eJB6Paper, "jisb6", 1512, 2150}, + { eJIS8K, "jis8K", 3154, 4606}, + { eJIS16K, "jis16K", 2303, 3154}, + { eJISExec, "jisexec", 2551, 3898}, + { eB6JIS, "B6 (JIS)", 1512, 2150}, + { eC6Envelope, "C6", 1345, 1912}, + { e8Kai, "8Kai", 3154, 4608}, + { e16Kai, "16Kai", 2304, 3154}, + { e12x18, "12x18", 3600, 5400}, + { e13x19_2, "13x19.2", 3900, 5758}, + { e13x19, "13x19", 3900, 5700}, + { e12_6x19_2, "12.6x19.2", 3779, 5758}, + { e12_6x18_5, "12.6x18.5", 3779, 5550}, + { e13x18, "13x18", 3900, 5400}, + { eSRA3, "SRA3", 3779, 5316}, + { eSRA4, "SRA4", 2658, 3779}, + { e226x310, "226x310", 2670, 3662}, + { e310x432, "310x432", 3662, 5104}, + { eEngQuatro, "EngQuatro", 2400, 3000}, + { e11x14, "11x14", 3300, 4200}, + { e11x15, "11x15", 3300, 4500}, + { e10x14, "10x14", 3000, 4200} +}; + +static void guess_paper_size(fz_pcl_options *pcl, int w, int h, int xres, int yres) +{ + int size; + int rotated = 0; + + /* If we've been given a paper size, live with it */ + if (pcl->paper_size != 0) + return; + + w = w * 300 / xres; + h = h * 300 / xres; + + /* Look for an exact match */ + for (size = 0; size < (int)nelem(papersizes); size++) + { + if (papersizes[size].code > eCustomPaperSize && (pcl->features & PCL_HAS_RICOH_PAPER_SIZES) == 0) + continue; + if (w == papersizes[size].width && h == papersizes[size].height) + break; + if ((pcl->features & PCL_HAS_ORIENTATION) && w == papersizes[size].height && h == papersizes[size].width) + { + rotated = 1; + break; + } + } + + /* If we didn't find an exact match, find the smallest one that's + * larger. Consider orientation if our printer supports it. */ + if (size == nelem(papersizes)) + { + if ((pcl->features & PCL_CAN_SET_CUSTOM_PAPER_SIZE) != 0) + { + /* Send it as a custom size */ + size = eCustomPaperSize; + } + else + { + /* Send the next larger one (minimise waste) */ + int i; + int best_waste = INT_MAX; + for (i = 0; i < (int)nelem(papersizes); i++) + { + int waste; + if (papersizes[i].code > eCustomPaperSize && (pcl->features & PCL_HAS_RICOH_PAPER_SIZES) == 0) + continue; + waste = papersizes[i].width * papersizes[i].height - w * h; + if (waste > best_waste) + continue; + if (w <= papersizes[i].width && h <= papersizes[i].height) + { + best_waste = waste; + rotated = 0; + size = i; + } + if ((pcl->features & PCL_HAS_ORIENTATION) && w <= papersizes[i].height && h <= papersizes[i].width) + { + best_waste = waste; + rotated = 1; + size = i; + } + } + } + } + + /* Now, size = The best size we have (or nelem(papersizes)) if it's too big */ + + if (size < (int)nelem(papersizes)) + pcl->paper_size = papersizes[size].code; + else + pcl->paper_size = eCustomPaperSize; /* Custom */ + + pcl->orientation = rotated; +} + +/* Copy a line, returning true if the line was blank. */ +static int +line_is_blank(unsigned char *dst, const unsigned char *sp, int w) +{ + int zero = 0; + + while (w-- > 0) + { + zero |= (*dst++ = *sp++); + zero |= (*dst++ = *sp++); + zero |= (*dst++ = *sp++); + } + + return zero == 0; +} + +static int +delta_compression(unsigned char *curr, unsigned char *prev, unsigned char *comp, int ds, int space) +{ + int left = space; + int x = ds; + + while (x > 0) + { + /* Count matching bytes */ + int match = 0; + int diff = 0; + while (x > 0 && *curr == *prev) + { + curr++; + prev++; + match++; + x--; + } + + /* Count different bytes */ + while (x > 0 && *curr != *prev) + { + curr++; + prev++; + diff++; + x--; + } + + while (diff > 0) + { + int exts; + int mini_diff = diff; + if (mini_diff > 8) + mini_diff = 8; + + exts = (match+255-31)/255; + left -= 1 + mini_diff + exts; + if (left < 0) + return 0; + *comp++ = ((mini_diff-1)<<5) | (match < 31 ? match : 31); + if (exts > 0) + { + match -= 31; + while (--exts) + { + *comp++ = 255; + match -= 255; + } + *comp++ = match; + } + memcpy(comp, curr-diff, mini_diff); + comp += mini_diff; + + match = 0; + diff -= mini_diff; + } + } + return space - left; +} + +void +fz_write_pixmap_as_pcl(fz_context *ctx, fz_output *out, const fz_pixmap *pixmap, const fz_pcl_options *pcl) +{ + fz_band_writer *writer; + + if (!pixmap || !out) + return; + + writer = fz_new_color_pcl_band_writer(ctx, out, pcl); + fz_try(ctx) + { + fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, pixmap->xres, pixmap->yres, 0, pixmap->colorspace, pixmap->seps); + fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples); + fz_close_band_writer(ctx, writer); + } + fz_always(ctx) + fz_drop_band_writer(ctx, writer); + fz_catch(ctx) + fz_rethrow(ctx); +} + +typedef struct color_pcl_band_writer_s +{ + fz_band_writer super; + fz_pcl_options options; + unsigned char *linebuf; + unsigned char compbuf[32768]; + unsigned char compbuf2[32768]; +} color_pcl_band_writer; + +static void +color_pcl_write_header(fz_context *ctx, fz_band_writer *writer_, fz_colorspace *cs) +{ + color_pcl_band_writer *writer = (color_pcl_band_writer *)writer_; + fz_output *out = writer->super.out; + int w = writer->super.w; + int h = writer->super.h; + int n = writer->super.n; + int s = writer->super.s; + int a = writer->super.alpha; + int xres = writer->super.xres; + int yres = writer->super.yres; + + if (a != 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "color PCL cannot write alpha channel"); + if (s != 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "color PCL cannot write spot colors"); + if (n != 3) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "color PCL must be RGB"); + + writer->linebuf = Memento_label(fz_malloc(ctx, w * 3 * 2), "color_pcl_linebuf"); + + guess_paper_size(&writer->options, w, h, xres, yres); + + pcl_header(ctx, out, &writer->options, 1, xres, yres, w, h); + + /* Raster presentation */ + /* Print in orientation of the logical page */ + fz_write_string(ctx, out, "\033&r0F"); + + /* Set color mode */ + fz_write_data(ctx, out, "\033*v6W" + "\000" /* Colorspace 0 = Device RGB */ + "\003" /* Pixel encoding mode: 3 = Direct by Pixel*/ + "\000" /* Bits per index: 0 = no palette */ + "\010" /* Red bits */ + "\010" /* Green bits */ + "\010", /* Blue bits */ + 11 + ); + + /* Raster resolution */ + /* Supposed to be strictly 75, 100, 150, 200, 300, 600 */ + /* FIXME: xres vs yres */ + fz_write_printf(ctx, out, "\033*t%dR", xres); +} + +static void flush_if_not_room(fz_context *ctx, fz_output *out, const unsigned char *comp, int *fill, int len) +{ + if (len + *fill >= 32767) + { + /* Can't fit any data, so flush */ + fz_write_printf(ctx, out, "\033*b%dW", *fill); + fz_write_data(ctx, out, comp, *fill); + *fill = 0; + } +} + +static void +color_pcl_compress_column(fz_context *ctx, color_pcl_band_writer *writer, const unsigned char *sp, int w, int h, int stride) +{ + fz_output *out = writer->super.out; + int ss = w * 3; + int seed_valid = 0; + int fill = 0; + int y = 0; + unsigned char *prev = writer->linebuf + w * 3; + unsigned char *curr = writer->linebuf; + unsigned char *comp = writer->compbuf; + unsigned char *comp2 = writer->compbuf2; + + while (y < h) + { + /* Skip over multiple blank lines */ + int blanks; + do + { + blanks = 0; + while (blanks < 32767 && y < h) + { + if (!line_is_blank(curr, sp, w)) + break; + blanks++; + y++; + sp += stride; + } + + if (blanks) + { + flush_if_not_room(ctx, out, comp, &fill, 3); + comp[fill++] = 4; /* Empty row */ + comp[fill++] = blanks>>8; + comp[fill++] = blanks & 0xFF; + seed_valid = 0; + } + } + while (blanks == 32767); + + if (y == h) + break; + + /* So, at least 1 more line to copy, and it's in curr */ + if (seed_valid && memcmp(curr, prev, ss) == 0) + { + int count = 1; + sp += stride; + y++; + while (count < 32767 && y < h) + { + if (memcmp(sp-stride, sp, ss) != 0) + break; + count++; + sp += stride; + y++; + } + flush_if_not_room(ctx, out, comp, &fill, 3); + comp[fill++] = 5; /* Duplicate row */ + comp[fill++] = count>>8; + comp[fill++] = count & 0xFF; + } + else + { + unsigned char *tmp; + int len = 0; + + /* Compress the line into our fixed buffer. */ + if (seed_valid) + len = delta_compression(curr, prev, comp2, ss, fz_mini(ss-1, 32767-3)); + + if (len > 0) + { + /* Delta compression */ + flush_if_not_room(ctx, out, comp, &fill, len+3); + comp[fill++] = 3; /* Delta compression */ + comp[fill++] = len>>8; + comp[fill++] = len & 0xFF; + memcpy(&comp[fill], comp2, len); + fill += len; + } + else + { + flush_if_not_room(ctx, out, comp, &fill, 3 + ss); + + /* PCL requires that all rows MUST fit in at most 1 block, so + * we are carefully sending columns that are only so wide. */ + + /* Unencoded */ + /* Transfer Raster Data: ss+3 bytes, 0 = Unencoded, count high, count low */ + comp[fill++] = 0; + comp[fill++] = ss>>8; + comp[fill++] = ss & 0xFF; + memcpy(&comp[fill], curr, ss); + fill += ss; + seed_valid = 1; + } + + /* curr becomes prev */ + tmp = prev; prev = curr; curr = tmp; + sp += stride; + y++; + } + } + /* And flush */ + if (fill) { + fz_write_printf(ctx, out, "\033*b%dW", fill); + fz_write_data(ctx, out, comp, fill); + } + + /* End Raster Graphics */ + fz_write_string(ctx, out, "\033*rC"); +} + +static void +color_pcl_write_band(fz_context *ctx, fz_band_writer *writer_, int stride, int band_start, int band_height, const unsigned char *sp) +{ + color_pcl_band_writer *writer = (color_pcl_band_writer *)writer_; + fz_output *out = writer->super.out; + int w = writer->super.w; + int h = writer->super.h; + int xres = writer->super.xres; + int cw; + int x; + + if (!out) + return; + + if (band_start+band_height >= h) + band_height = h - band_start; + + /* We have to specify image output size in decipoints (720dpi). + * Most usual PCL resolutions are a multiple of 75. + * Pick our maximum column size to be 10800 = 15*720 = 144*75 + * to give us good results. 10800 * 3 = 32400 < 32760 */ + cw = 10800; /* Limited by how much rowdata we can send at once */ + if (cw > w) + cw = w; + + for (x = 0; x*cw < w; x++) + { + int col_w = w - cw*x; + if (col_w > cw) + col_w = cw; + + /* Top left corner */ + fz_write_printf(ctx, out, "\033*p%dx%dY", x*cw, band_start); + + /* Raster height */ + fz_write_printf(ctx, out, "\033*r%dT", band_height); + + /* Raster width */ + fz_write_printf(ctx, out, "\033*r%dS", col_w); + + /* Destination height */ + fz_write_printf(ctx, out, "\033*t%dV", band_height*720/xres); + + /* Destination width */ + fz_write_printf(ctx, out, "\033*t%dH", col_w*720/xres); + + /* start raster graphics */ + /* 1 = start at cursor position */ + fz_write_string(ctx, out, "\033*r3A"); + + /* Now output the actual bitmap */ + /* Adaptive Compression */ + fz_write_string(ctx, out, "\033*b5M"); + + color_pcl_compress_column(ctx, writer, sp + x * cw * 3, col_w, band_height, stride); + } +} + +static void +color_pcl_write_trailer(fz_context *ctx, fz_band_writer *writer_) +{ +} + +static void +color_pcl_drop_band_writer(fz_context *ctx, fz_band_writer *writer_) +{ + color_pcl_band_writer *writer = (color_pcl_band_writer *)writer_; + fz_free(ctx, writer->linebuf); +} + +fz_band_writer *fz_new_color_pcl_band_writer(fz_context *ctx, fz_output *out, const fz_pcl_options *options) +{ + color_pcl_band_writer *writer = fz_new_band_writer(ctx, color_pcl_band_writer, out); + + writer->super.header = color_pcl_write_header; + writer->super.band = color_pcl_write_band; + writer->super.trailer = color_pcl_write_trailer; + writer->super.drop = color_pcl_drop_band_writer; + + if (options) + writer->options = *options; + else + fz_pcl_preset(ctx, &writer->options, "generic"); + + return &writer->super; +} + +/* + * Mode 2 Row compression routine for the HP DeskJet & LaserJet IIp. + * Compresses data from row up to end_row, storing the result + * starting at out. Returns the number of bytes stored. + * Runs of K<=127 literal bytes are encoded as K-1 followed by + * the bytes; runs of 2<=K<=127 identical bytes are encoded as + * 257-K followed by the byte. + * In the worst case, the result is N+(N/127)+1 bytes long, + * where N is the original byte count (end_row - row). + */ +static int +mode2compress(unsigned char *out, const unsigned char *in, int in_len) +{ + int x; + int out_len = 0; + int run; + + for (x = 0; x < in_len; x += run) + { + /* How far do we have to look to find a value that isn't repeated? */ + for (run = 1; run < 127 && x+run < in_len; run++) + if (in[0] != in[run]) + break; + if (run > 1) + { + /* We have a run of matching bytes */ + out[out_len++] = 257-run; + out[out_len++] = in[0]; + } + else + { + /* Now copy as many literals as possible. We only + * break the run at a length of 127, at the end, + * or where we have 3 repeated values. */ + int i; + + /* How many literals do we need to copy? */ + for (; run < 127 && x+run+2 < in_len; run++) + if (in[run] == in[run+1] && in[run] == in[run+2]) + break; + /* Don't leave stragglers at the end */ + if (x + run + 2 >= in_len) + { + run = in_len - x; + if (run > 127) + run = 127; + } + out[out_len++] = run-1; + for (i = 0; i < run; i++) + { + out[out_len++] = in[i]; + } + } + in += run; + } + + return out_len; +} + +/* + * Mode 3 compression routine for the HP LaserJet III family. + * Compresses bytecount bytes starting at current, storing the result + * in compressed, comparing against and updating previous. + * Returns the number of bytes stored. In the worst case, + * the number of bytes is bytecount+(bytecount/8)+1. + */ +static int +mode3compress(unsigned char *out, const unsigned char *in, unsigned char *prev, int in_len) +{ + unsigned char *compressed = out; + const unsigned char *cur = in; + const unsigned char *end = in + in_len; + + while (cur < end) { /* Detect a maximum run of unchanged bytes. */ + const unsigned char *run = cur; + const unsigned char *diff; + const unsigned char *stop; + int offset, cbyte; + + while (cur < end && *cur == *prev) { + cur++, prev++; + } + if (cur == end) + break; /* rest of row is unchanged */ + /* Detect a run of up to 8 changed bytes. */ + /* We know that *cur != *prev. */ + diff = cur; + stop = (end - cur > 8 ? cur + 8 : end); + do + { + *prev++ = *cur++; + } + while (cur < stop && *cur != *prev); + /* Now [run..diff) are unchanged, and */ + /* [diff..cur) are changed. */ + /* Generate the command byte(s). */ + offset = diff - run; + cbyte = (cur - diff - 1) << 5; + if (offset < 31) + *out++ = cbyte + offset; + else { + *out++ = cbyte + 31; + offset -= 31; + while (offset >= 255) + *out++ = 255, offset -= 255; + *out++ = offset; + } + /* Copy the changed data. */ + while (diff < cur) + *out++ = *diff++; + } + return out - compressed; +} + +void +fz_write_bitmap_as_pcl(fz_context *ctx, fz_output *out, const fz_bitmap *bitmap, const fz_pcl_options *pcl) +{ + fz_band_writer *writer; + + if (!bitmap || !out) + return; + + writer = fz_new_mono_pcl_band_writer(ctx, out, pcl); + fz_try(ctx) + { + fz_write_header(ctx, writer, bitmap->w, bitmap->h, 1, 0, bitmap->xres, bitmap->yres, 0, NULL, NULL); + fz_write_band(ctx, writer, bitmap->stride, bitmap->h, bitmap->samples); + fz_close_band_writer(ctx, writer); + } + fz_always(ctx) + fz_drop_band_writer(ctx, writer); + fz_catch(ctx) + fz_rethrow(ctx); +} + +typedef struct mono_pcl_band_writer_s +{ + fz_band_writer super; + fz_pcl_options options; + unsigned char *prev; + unsigned char *mode2buf; + unsigned char *mode3buf; + int top_of_page; + int num_blank_lines; +} mono_pcl_band_writer; + +static void +mono_pcl_write_header(fz_context *ctx, fz_band_writer *writer_, fz_colorspace *cs) +{ + mono_pcl_band_writer *writer = (mono_pcl_band_writer *)writer_; + fz_output *out = writer->super.out; + int w = writer->super.w; + int h = writer->super.h; + int xres = writer->super.xres; + int yres = writer->super.yres; + int line_size; + int max_mode_2_size; + int max_mode_3_size; + + if (writer->super.alpha != 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "mono PCL cannot write alpha channel"); + if (writer->super.s != 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "mono PCL cannot write spot colors"); + if (writer->super.n != 1) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "mono PCL must be grayscale"); + + line_size = (w + 7)/8; + max_mode_2_size = line_size + (line_size/127) + 1; + max_mode_3_size = line_size + (line_size/8) + 1; + + writer->prev = fz_calloc(ctx, line_size, sizeof(unsigned char)); + writer->mode2buf = fz_calloc(ctx, max_mode_2_size, sizeof(unsigned char)); + writer->mode3buf = fz_calloc(ctx, max_mode_3_size, sizeof(unsigned char)); + writer->num_blank_lines = 0; + writer->top_of_page = 1; + + guess_paper_size(&writer->options, w, h, xres, yres); + + if (writer->options.features & HACK__IS_A_OCE9050) + { + /* Enter HPGL/2 mode, begin plot, Initialise (start plot), Enter PCL mode */ + fz_write_string(ctx, out, "\033%1BBPIN;\033%1A"); + } + + pcl_header(ctx, out, &writer->options, 1, xres, yres, w, h); +} + +static void +mono_pcl_write_band(fz_context *ctx, fz_band_writer *writer_, int ss, int band_start, int band_height, const unsigned char *data) +{ + mono_pcl_band_writer *writer = (mono_pcl_band_writer *)writer_; + fz_output *out = writer->super.out; + int w = writer->super.w; + int yres = writer->super.yres; + const unsigned char *out_data; + int y, rmask, line_size; + int num_blank_lines; + int compression = -1; + unsigned char *prev = NULL; + unsigned char *mode2buf = NULL; + unsigned char *mode3buf = NULL; + int out_count; + const fz_pcl_options *pcl; + + if (!out) + return; + + num_blank_lines = writer->num_blank_lines; + rmask = ~0 << (-w & 7); + line_size = (w + 7)/8; + prev = writer->prev; + mode2buf = writer->mode2buf; + mode3buf = writer->mode3buf; + pcl = &writer->options; + + /* Transfer raster graphics. */ + for (y = 0; y < band_height; y++, data += ss) + { + const unsigned char *end_data = data + line_size; + + if ((end_data[-1] & rmask) == 0) + { + end_data--; + while (end_data > data && end_data[-1] == 0) + end_data--; + } + if (end_data == data) + { + /* Blank line */ + num_blank_lines++; + continue; + } + + /* We've reached a non-blank line. */ + /* Put out a spacing command if necessary. */ + if (writer->top_of_page) + { + writer->top_of_page = 0; + /* We're at the top of a page. */ + if (pcl->features & PCL_ANY_SPACING) + { + if (num_blank_lines > 0) + fz_write_printf(ctx, out, "\033*p+%dY", num_blank_lines); + /* Start raster graphics. */ + fz_write_string(ctx, out, "\033*r1A"); + } + else if (pcl->features & PCL_MODE_3_COMPRESSION) + { + /* Start raster graphics. */ + fz_write_string(ctx, out, "\033*r1A"); + for (; num_blank_lines; num_blank_lines--) + fz_write_string(ctx, out, "\033*b0W"); + } + else + { + /* Start raster graphics. */ + fz_write_string(ctx, out, "\033*r1A"); + for (; num_blank_lines; num_blank_lines--) + fz_write_string(ctx, out, "\033*bW"); + } + } + + /* Skip blank lines if any */ + else if (num_blank_lines != 0) + { + /* Moving down from current position causes head + * motion on the DeskJet, so if the number of lines + * is small, we're better off printing blanks. + * + * For Canon LBP4i and some others, <ESC>*b<n>Y + * doesn't properly clear the seed row if we are in + * compression mode 3. + */ + if ((num_blank_lines < MIN_SKIP_LINES && compression != 3) || + !(pcl->features & PCL_ANY_SPACING)) + { + int mode_3ns = ((pcl->features & PCL_MODE_3_COMPRESSION) && !(pcl->features & PCL_ANY_SPACING)); + if (mode_3ns && compression != 2) + { + /* Switch to mode 2 */ + fz_write_string(ctx, out, from3to2); + compression = 2; + } + if (pcl->features & PCL_MODE_3_COMPRESSION) + { + /* Must clear the seed row. */ + fz_write_string(ctx, out, "\033*b1Y"); + num_blank_lines--; + } + if (mode_3ns) + { + for (; num_blank_lines; num_blank_lines--) + fz_write_string(ctx, out, "\033*b0W"); + } + else + { + for (; num_blank_lines; num_blank_lines--) + fz_write_string(ctx, out, "\033*bW"); + } + } + else if (pcl->features & PCL3_SPACING) + fz_write_printf(ctx, out, "\033*p+%dY", num_blank_lines * yres); + else + fz_write_printf(ctx, out, "\033*b%dY", num_blank_lines); + /* Clear the seed row (only matters for mode 3 compression). */ + memset(prev, 0, line_size); + } + num_blank_lines = 0; + + /* Choose the best compression mode for this particular line. */ + if (pcl->features & PCL_MODE_3_COMPRESSION) + { + /* Compression modes 2 and 3 are both available. Try + * both and see which produces the least output data. + */ + int count3 = mode3compress(mode3buf, data, prev, line_size); + int count2 = mode2compress(mode2buf, data, line_size); + int penalty3 = (compression == 3 ? 0 : penalty_from2to3); + int penalty2 = (compression == 2 ? 0 : penalty_from3to2); + + if (count3 + penalty3 < count2 + penalty2) + { + if (compression != 3) + fz_write_string(ctx, out, from2to3); + compression = 3; + out_data = (unsigned char *)mode3buf; + out_count = count3; + } + else + { + if (compression != 2) + fz_write_string(ctx, out, from3to2); + compression = 2; + out_data = (unsigned char *)mode2buf; + out_count = count2; + } + } + else if (pcl->features & PCL_MODE_2_COMPRESSION) + { + out_data = mode2buf; + out_count = mode2compress(mode2buf, data, line_size); + } + else + { + out_data = data; + out_count = line_size; + } + + /* Transfer the data */ + fz_write_printf(ctx, out, "\033*b%dW", out_count); + fz_write_data(ctx, out, out_data, out_count); + } + + writer->num_blank_lines = num_blank_lines; +} + +static void +mono_pcl_write_trailer(fz_context *ctx, fz_band_writer *writer_) +{ + mono_pcl_band_writer *writer = (mono_pcl_band_writer *)writer_; + fz_output *out = writer->super.out; + + /* end raster graphics and eject page */ + fz_write_string(ctx, out, "\033*rB\f"); + + if (writer->options.features & HACK__IS_A_OCE9050) + { + /* Pen up, pen select, advance full page, reset */ + fz_write_string(ctx, out, "\033%1BPUSP0PG;\033E"); + } +} + +static void +mono_pcl_drop_band_writer(fz_context *ctx, fz_band_writer *writer_) +{ + mono_pcl_band_writer *writer = (mono_pcl_band_writer *)writer_; + + fz_free(ctx, writer->prev); + fz_free(ctx, writer->mode2buf); + fz_free(ctx, writer->mode3buf); +} + +fz_band_writer *fz_new_mono_pcl_band_writer(fz_context *ctx, fz_output *out, const fz_pcl_options *options) +{ + mono_pcl_band_writer *writer = fz_new_band_writer(ctx, mono_pcl_band_writer, out); + + writer->super.header = mono_pcl_write_header; + writer->super.band = mono_pcl_write_band; + writer->super.trailer = mono_pcl_write_trailer; + writer->super.drop = mono_pcl_drop_band_writer; + + if (options) + writer->options = *options; + else + fz_pcl_preset(ctx, &writer->options, "generic"); + + return &writer->super; +} + +void +fz_save_pixmap_as_pcl(fz_context *ctx, fz_pixmap *pixmap, char *filename, int append, const fz_pcl_options *pcl) +{ + fz_output *out = fz_new_output_with_path(ctx, filename, append); + fz_try(ctx) + { + fz_write_pixmap_as_pcl(ctx, out, pixmap, pcl); + fz_close_output(ctx, out); + } + fz_always(ctx) + fz_drop_output(ctx, out); + fz_catch(ctx) + fz_rethrow(ctx); +} + +void +fz_save_bitmap_as_pcl(fz_context *ctx, fz_bitmap *bitmap, char *filename, int append, const fz_pcl_options *pcl) +{ + fz_output *out = fz_new_output_with_path(ctx, filename, append); + fz_try(ctx) + { + fz_write_bitmap_as_pcl(ctx, out, bitmap, pcl); + fz_close_output(ctx, out); + } + fz_always(ctx) + fz_drop_output(ctx, out); + fz_catch(ctx) + fz_rethrow(ctx); +} + +/* High-level document writer interface */ + +typedef struct +{ + fz_document_writer super; + fz_draw_options draw; + fz_pcl_options pcl; + fz_pixmap *pixmap; + int mono; + fz_output *out; +} fz_pcl_writer; + +static fz_device * +pcl_begin_page(fz_context *ctx, fz_document_writer *wri_, fz_rect mediabox) +{ + fz_pcl_writer *wri = (fz_pcl_writer*)wri_; + return fz_new_draw_device_with_options(ctx, &wri->draw, mediabox, &wri->pixmap); +} + +static void +pcl_end_page(fz_context *ctx, fz_document_writer *wri_, fz_device *dev) +{ + fz_pcl_writer *wri = (fz_pcl_writer*)wri_; + fz_bitmap *bitmap = NULL; + + fz_var(bitmap); + + fz_try(ctx) + { + fz_close_device(ctx, dev); + if (wri->mono) + { + bitmap = fz_new_bitmap_from_pixmap(ctx, wri->pixmap, NULL); + fz_write_bitmap_as_pcl(ctx, wri->out, bitmap, &wri->pcl); + } + else + { + fz_write_pixmap_as_pcl(ctx, wri->out, wri->pixmap, &wri->pcl); + } + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + fz_drop_bitmap(ctx, bitmap); + fz_drop_pixmap(ctx, wri->pixmap); + wri->pixmap = NULL; + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +static void +pcl_close_writer(fz_context *ctx, fz_document_writer *wri_) +{ + fz_pcl_writer *wri = (fz_pcl_writer*)wri_; + fz_close_output(ctx, wri->out); +} + +static void +pcl_drop_writer(fz_context *ctx, fz_document_writer *wri_) +{ + fz_pcl_writer *wri = (fz_pcl_writer*)wri_; + fz_drop_pixmap(ctx, wri->pixmap); + fz_drop_output(ctx, wri->out); +} + +fz_document_writer * +fz_new_pcl_writer_with_output(fz_context *ctx, fz_output *out, const char *options) +{ + fz_pcl_writer *wri = NULL; + const char *val; + + fz_var(wri); + + fz_try(ctx) + { + wri = fz_new_derived_document_writer(ctx, fz_pcl_writer, pcl_begin_page, pcl_end_page, pcl_close_writer, pcl_drop_writer); + fz_parse_draw_options(ctx, &wri->draw, options); + fz_parse_pcl_options(ctx, &wri->pcl, options); + if (fz_has_option(ctx, options, "colorspace", &val)) + if (fz_option_eq(val, "mono")) + wri->mono = 1; + wri->out = out; + } + fz_catch(ctx) + { + fz_drop_output(ctx, out); + fz_free(ctx, wri); + fz_rethrow(ctx); + } + + return (fz_document_writer*)wri; +} + +fz_document_writer * +fz_new_pcl_writer(fz_context *ctx, const char *path, const char *options) +{ + fz_output *out = fz_new_output_with_path(ctx, path ? path : "out.pcl", 0); + return fz_new_pcl_writer_with_output(ctx, out, options); +}
