/* * ------------------------------------------------------------- * * (C) 2002 Jochen Karrer, Linuxdata GbR * * convert a pnm to PCL-XL image * * ------------------------------------------------------------- */ /* Engineering note: One PCL-XL printer prints an error message like this when it doesn't like the PCL it sees: PCL XL error Subsystem: IMAGE Error: IllegalAttributeValue Operator: ReadImage Position: 8 "Position" is the sequence number of the PCL operator it was trying to execute. */ #include #include #include #include #include #include #include #include "pm_c_util.h" #include "pam.h" #include "shhopt.h" #include "mallocvar.h" #include "nstring.h" #include "pclxl.h" #define PAPERWIDTH(format) (xlPaperFormats[format].width) #define PAPERHEIGHT(format) (xlPaperFormats[format].height) typedef struct InputSource { const char * name; struct InputSource * next; } InputSource; struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ InputSource * sourceP; int dpi; enum MediaSize format; unsigned int feederSpec; int feeder; unsigned int outtraySpec; int outtray; unsigned int duplexSpec; enum DuplexPageMode duplex; unsigned int copiesSpec; int copies; unsigned int center; float xoffs; float yoffs; unsigned int colorok; unsigned int verbose; const char * jobsetup; /* -jobsetup option value. NULL if none */ unsigned int rendergray; }; static void parseCommandLine(int argc, char ** argv, struct cmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- parse program command line described in Unix standard form by argc and argv. Return the information in the options as *cmdlineP. If command line is internally inconsistent (invalid options, etc.), issue error message to stderr and abort program. Note that the strings we return are stored in the storage that was passed to us as the argv array. We also trash *argv. -----------------------------------------------------------------------------*/ optEntry *option_def = malloc( 100*sizeof( optEntry ) ); /* Instructions to optParseOptions3 on how to parse our options. */ optStruct3 opt; unsigned int option_def_index; char *formatOpt; char *duplexOpt; unsigned int dpiSpec, xoffsSpec, yoffsSpec, formatSpec, jobsetupSpec; option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "dpi", OPT_UINT, &cmdlineP->dpi, &dpiSpec, 0); OPTENT3(0, "xoffs", OPT_FLOAT, &cmdlineP->xoffs, &xoffsSpec, 0); OPTENT3(0, "yoffs", OPT_FLOAT, &cmdlineP->yoffs, &yoffsSpec, 0); OPTENT3(0, "format", OPT_STRING, &formatOpt, &formatSpec, 0); OPTENT3(0, "duplex", OPT_STRING, &duplexOpt, &cmdlineP->duplexSpec, 0); OPTENT3(0, "copies", OPT_UINT, &cmdlineP->copies, &cmdlineP->copiesSpec, 0); OPTENT3(0, "colorok", OPT_FLAG, NULL, &cmdlineP->colorok, 0); OPTENT3(0, "center", OPT_FLAG, NULL, &cmdlineP->center, 0 ); OPTENT3(0, "feeder", OPT_UINT, &cmdlineP->feeder, &cmdlineP->feederSpec, 0); OPTENT3(0, "outtray", OPT_UINT, &cmdlineP->outtray, &cmdlineP->outtraySpec, 0); OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); OPTENT3(0, "jobsetup", OPT_STRING, &cmdlineP->jobsetup, &jobsetupSpec, 0); OPTENT3(0, "rendergray", OPT_FLAG, NULL, &cmdlineP->rendergray, 0 ); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ optParseOptions3( &argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (!dpiSpec) cmdlineP->dpi = 300; if (!xoffsSpec) cmdlineP->xoffs = 0.0; if (!yoffsSpec) cmdlineP->yoffs = 0.0; if (cmdlineP->duplexSpec) { if (strncmp(duplexOpt, "vertical", strlen(duplexOpt)) == 0) cmdlineP->duplex = eDuplexVerticalBinding; else if (strncmp(duplexOpt, "horizontal", strlen(duplexOpt)) == 0) cmdlineP->duplex = eDuplexHorizontalBinding; else pm_error("Invalid value '%s' for -duplex option", duplexOpt); } if (formatSpec) { bool found; int i; for (i = 0, found=FALSE; xlPaperFormats[i].name && !found; ++i) { if (streq(xlPaperFormats[i].name, formatOpt)) { found = TRUE; cmdlineP->format = xlPaperFormats[i].xl_nr; } } if (!found) { int i; pm_message("Valid -format values:"); for (i = 0; xlPaperFormats[i].name; ++i) { if (xlPaperFormats[i].width > 0) pm_message(" %s", xlPaperFormats[i].name); } pm_error("Invalid -format option '%s' specified.", formatOpt); } } else cmdlineP->format = eLetterPaper; if (!jobsetupSpec) cmdlineP->jobsetup = NULL; if (argc-1 < 1) { MALLOCVAR(cmdlineP->sourceP); cmdlineP->sourceP->name = "-"; cmdlineP->sourceP->next = NULL; } else { unsigned int i; InputSource ** nextLinkP; nextLinkP = &cmdlineP->sourceP; for (i = 1; i < argc; ++i) { InputSource * sourceP; MALLOCVAR(sourceP); sourceP->name = argv[i]; *nextLinkP = sourceP; nextLinkP = &sourceP->next; *nextLinkP = NULL; } } } #define XY_RLE_FBUFSIZE (1024) typedef struct XY_rle { int error; unsigned char buf[128]; int bpos; int state; unsigned char *fbuf; int fbpos; int fbufsize; int fd; } XY_rle; static void XY_RLEreset(XY_rle *rle) { rle->state = eSTART; rle->bpos = 0; rle->fbpos=0; rle->error=0; } static XY_rle * XY_RLEnew(int size) { XY_rle *rle; MALLOCVAR(rle); if(rle==NULL) return rle; if(size<1024) size=1024; rle->fbuf=malloc(size); rle->fbufsize=size; if(!rle->fbuf) { free(rle); return NULL; } return rle; } static void XY_RLEdelete(XY_rle *rle) { free(rle->fbuf); free(rle); } static int out(XY_rle *rle,int count) { if(rle->state==eRLE) { rle->fbuf[rle->fbpos++]=-count+1; rle->fbuf[rle->fbpos++]=rle->buf[0]; } else if(rle->bpos>0) { rle->fbuf[rle->fbpos++]=count-1; memcpy(rle->fbuf+rle->fbpos,rle->buf,count); rle->fbpos+=count; } if(rle->fbpos+129>rle->fbufsize) { if (rle->fbufsize > INT_MAX/1.2) pm_error("Arithmetic overflow during attempt to expand RLE " "output buffer beyond %u", rle->fbufsize); rle->fbufsize*=1.2; rle->fbuf=realloc(rle->fbuf,rle->fbufsize); if(rle->fbuf==NULL) { pm_error("Out of memory while attempting to expand RLE " "output buffer beyond %u", rle->fbufsize); rle->error=-1; rle->fbpos=0; return -1; } } rle->bpos=0; rle->state=eSTART; return 0; } static int XY_RLEfinish (XY_rle *rle) { out(rle,rle->bpos); if(rle->error<0) return rle->error; else return rle->fbpos; } static void rle_putbyte(XY_rle *rle,unsigned char u) { switch (rle->state) { case eRLE: if(u!=rle->buf[0]) { out(rle,rle->bpos); } break; case eLIT: if((u==rle->buf[rle->bpos-1])&&(u==rle->buf[rle->bpos-2])) { out(rle,rle->bpos-2); rle->buf[0]=u; rle->bpos+=2; rle->state=eRLE; } break; case eSTART: if(rle->bpos==1) { if(u==rle->buf[rle->bpos-1]) { rle->state=eRLE; } else { rle->state=eLIT; } } break; } rle->buf[rle->bpos++]=u; if(rle->bpos==128) { out(rle,rle->bpos); } } static void XY_RLEput(XY_rle *rle,const unsigned char buf[],int count) { int i; for(i=0;i>8; return XY_Write(fd,c,2); } static int out_uint32(int fd,unsigned int data ) { unsigned char c[4]; c[0] = data&0xff; c[1]=(data>>8)&0xff; c[2]=(data>>16)&0xff; c[3]=data>>24; return XY_Write(fd,c,4); } static int out_sint16(int fd,signed short sdata ) { unsigned short data=(unsigned short)sdata; unsigned char c[2]; c[0]=data&0xff; c[1]=data>>8; return XY_Write(fd,c,2); } #if 0 static int out_sint32(int fd,signed int sdata ) { unsigned int data=(unsigned int)sdata; unsigned char c[4]; c[0] = data&0xff; c[1]=(data>>8)&0xff; c[2]=(data>>16)&0xff; c[3]=data>>24; return XY_Write(fd,c,4); } #endif static int xl_ubyte(int fd,unsigned char data) { unsigned char const tag=0xc0; XY_Write(fd,&tag,1); return out_ubyte(fd,data); } static int xl_uint16(int fd,unsigned short data ) { unsigned char const tag=0xc1; XY_Write(fd,&tag,1); return out_uint16(fd,data); } #if 0 static int xl_uint32(int fd,unsigned int data ) { unsigned char const c=0xc2; XY_Write(fd,&c,1); return out_uint32(fd,data); } static int xl_sint16(int fd,signed short data ) { unsigned char const c=0xc3; XY_Write(fd,&c,1); return out_sint16(fd,data); } static int xl_sint32(int fd,signed int data ) { unsigned char const c=0xc4; XY_Write(fd,&c,1); return out_sint32(fd,data); } #endif static int xl_ubyte_array(int const fd, const unsigned char * const data, int const len) { unsigned int i; unsigned char head[4]; head[0] = 0xc8; head[1] = 0xc1; head[2] = len&0xff; head[3] = (len>>8)&0xff; XY_Write(fd, head, 4); for (i = 0; i < len; ++i) out_ubyte(fd, data[i]); return 0; } #if 0 static int xl_uint16_array(int fd,unsigned short *data,int len) { int i; unsigned char head[4]; head[0]=0xc9;head[1]=0xc1;head[2]=len&0xff;head[3]=(len>>8)&0xff; XY_Write(fd,head,4); for(i=0;i>8)&0xff; XY_Write(fd,head,4); for(i=0;i>8)&0xff; XY_Write(fd,head,4); for(i=0;i>8)&0xff; XY_Write(fd,head,4); for(i=0;ilinelen+3)/4)*4; int rlelen; unsigned int line; XY_RLEreset(rle); for (line = firstLine; line < firstLine + nlines; ++line) { pclGeneratorP->getnextrow(pclGeneratorP, pamP); XY_RLEput(rle, pclGeneratorP->data, pclGeneratorP->linelen); XY_RLEput(rle, pad, paddedLinelen - pclGeneratorP->linelen); } rlelen = XY_RLEfinish(rle); if (rlelen<0) pm_error("Error on Making rle"); xl_dataLength(outFd, rlelen); XY_Write(outFd, rle->fbuf, rlelen); } /* * ------------------------------------------------------------ * XL_WriteImage * Write a PCL-XL image to the datastream * ------------------------------------------------------------ */ static void convertAndWriteImage(int const outFd, const pclGenerator * const pclGenP, struct pam * const pamP) { int blockStartLine; XY_rle * rle; xl_ubyte(outFd, eDirectPixel); xl_attr_ubyte(outFd, aColorMapping); xl_ubyte(outFd, pclGenP->colorDepth); xl_attr_ubyte(outFd, aColorDepth); xl_uint16(outFd, pclGenP->width); xl_attr_ubyte(outFd, aSourceWidth); xl_uint16(outFd, pclGenP->height); xl_attr_ubyte(outFd, aSourceHeight); xl_uint16_xy(outFd, pclGenP->width*1, pclGenP->height*1); xl_attr_ubyte(outFd, aDestinationSize); XL_Operator(outFd, oBeginImage); if (pclGenP->linelen > INT_MAX / 20) pm_error("Image too big"); rle = XY_RLEnew(pclGenP->linelen*20); if (!rle) pm_error("Unable to allocate %d bytes for the RLE buffer", pclGenP->linelen * 20); blockStartLine = 0; while (blockStartLine < pclGenP->height) { unsigned int const blockHeight = MIN(20, pclGenP->height-blockStartLine); xl_uint16(outFd, blockStartLine); xl_attr_ubyte(outFd, aStartLine); xl_uint16(outFd, blockHeight); xl_attr_ubyte(outFd, aBlockHeight); xl_ubyte(outFd, eRLECompression); xl_attr_ubyte(outFd, aCompressMode); /* In modern PCL-XL, we could use a PadBytesMultiple attribute here to avoid having to pad the data to a multiple of 4 bytes. But PCL-XL 1.1 didn't have PadBytesMultiple. xl_ubyte(outFd, 1); xl_attr_ubyte(outFd, aPadBytesMultiple); */ XL_Operator(outFd, oReadImage); convertAndWriteRleBlock(outFd, pclGenP, pamP, blockStartLine, blockHeight, rle); blockStartLine += blockHeight; } XY_RLEdelete(rle); XL_Operator(outFd, oEndImage); } static void endPage(int const outFd, bool const doCopies, unsigned int const copies) { /*---------------------------------------------------------------------------- Emit an EndPage printer command. -----------------------------------------------------------------------------*/ if (doCopies) { /* wrong in example in PCL-XL manual. Type is uint16 ! */ xl_uint16(outFd, copies); xl_attr_ubyte(outFd, aPageCopies); } XL_Operator(outFd, oEndPage); } static void convertAndPrintPage(int const outFd, const pclGenerator * const pclGeneratorP, struct pam * const pamP, enum MediaSize const format, int const dpi, bool const center, float const xoffs, float const yoffs, bool const doDuplex, enum DuplexPageMode const duplex, bool const doCopies, unsigned int const copies, bool const doMediaSource, int const mediaSource, bool const doMediaDestination, int const mediaDestination) { beginPage(outFd, doDuplex, duplex, doMediaSource, mediaSource, doMediaDestination, mediaDestination, format); /* Before Netpbm 10.27 (March 2005), we always set up a two-byte 8 bit deep palette: {0, 255}. I don't know why, because this works only for e1Bit color depth an eGray color space, and in that case does the same thing as having no palette at all. But in other cases, it doesn't work. E.g. with eRGB, e8Bit, we got an IllegalArraySize error from the printer on the SetColorSpace command. So we don't use a palette at all now. */ setColorSpace(outFd, pclGeneratorP->colorSpace, NULL, 0, 0); positionCursor(outFd, center, xoffs, yoffs, pclGeneratorP->width, pclGeneratorP->height, dpi, format); convertAndWriteImage(outFd, pclGeneratorP, pamP); endPage(outFd, doCopies, copies); } static void beginSession(int const outFd, unsigned int const xdpi, unsigned int const ydpi, enum Measure const measure, bool const noReporting, enum ErrorReport const errorReport) { xl_uint16_xy(outFd, xdpi, ydpi); xl_attr_ubyte(outFd, aUnitsPerMeasure); xl_ubyte(outFd, measure); xl_attr_ubyte(outFd, aMeasure); /* xl_ubyte(outFd,eNoReporting); xl_attr_ubyte(outFd,aErrorReport); */ xl_ubyte(outFd,errorReport); xl_attr_ubyte(outFd,aErrorReport); XL_Operator(outFd,oBeginSession); } static void endSession(int outFd) { XL_Operator(outFd,oEndSession); } static void pnmToPcllinePackbits(const pclGenerator * const pclGeneratorP, struct pam * const pamP) { tuple * tuplerow; unsigned int pcl_cursor; unsigned char accum; unsigned char bitmask; unsigned int col; tuplerow = pnm_allocpamrow(pamP); pnm_readpamrow(pamP, tuplerow); pcl_cursor = 0; bitmask = 0x80; accum = 0x00; for (col = 0; col < pamP->width; ++col) { if (tuplerow[col][0] == PAM_PBM_WHITE) accum |= bitmask; bitmask >>= 1; if (bitmask == 0) { pclGeneratorP->data[pcl_cursor++] = accum; bitmask = 0x80; accum = 0x0; } } if (bitmask != 0x80) pclGeneratorP->data[pcl_cursor++] = accum; pnm_freepamrow(tuplerow); } static void createPclGeneratorPackbits(struct pam * const pamP, pclGenerator ** const pclGeneratorPP) { /* Samples are black or white and packed 8 to a byte */ pclGenerator * pclGeneratorP; MALLOCVAR_NOFAIL(pclGeneratorP); pclGeneratorP->colorDepth = e1Bit; pclGeneratorP->colorSpace = eGray; pclGeneratorP->linelen = (pamP->width+7)/8; pclGeneratorP->height = pamP->height; pclGeneratorP->width = (pamP->width); pclGeneratorP->data = malloc(pclGeneratorP->linelen); if (pclGeneratorP->data == NULL) pm_error("Unable to allocate row buffer."); pclGeneratorP->getnextrow = pnmToPcllinePackbits; *pclGeneratorPP = pclGeneratorP; } static void pnmToPcllineWholebytes(const pclGenerator * const pclGeneratorP, struct pam * const pamP) { tuple * tuplerow; unsigned int pcl_cursor; unsigned int col; tuplerow = pnm_allocpamrow(pamP); pnm_readpamrow(pamP, tuplerow); pcl_cursor = 0; /* initial value */ for (col = 0; col < pamP->width; ++col) { unsigned int plane; for (plane = 0; plane < pamP->depth; ++plane) { pclGeneratorP->data[pcl_cursor++] = pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255); } } pnm_freepamrow(tuplerow); } static void createPclGeneratorWholebytes(struct pam * const pamP, pclGenerator ** const pclGenPP) { /* One sample per byte */ pclGenerator * pclGenP; MALLOCVAR_NOFAIL(pclGenP); if (pamP->depth < 3) pclGenP->colorSpace = eGray; else pclGenP->colorSpace = eRGB; pclGenP->colorDepth = e8Bit; pclGenP->height = pamP->height; pclGenP->width = pamP->width; pclGenP->linelen = pamP->width * pamP->depth; if (UINT_MAX / pamP->width < pamP->depth) pm_error("Image to big to process"); else pclGenP->data = malloc(pamP->width * pamP->depth); if (pclGenP->data == NULL) pm_error("Unable to allocate row buffer."); pclGenP->getnextrow = pnmToPcllineWholebytes; *pclGenPP = pclGenP; } static void destroyPclGenerator(pclGenerator * const pclGenP) { free(pclGenP->data); free(pclGenP); } static void createPclGenerator(struct pam * const pamP, pclGenerator ** const pclGeneratorPP, bool const colorok) { if (pamP->depth > 1 && !colorok) pm_message("WARNING: generating a color print stream because the " "input image is PPM. " "To generate a black and white print stream, run the input " "through Ppmtopgm. To suppress this warning, use the " "-colorok option."); if (pamP->depth == 1 && pamP->maxval == 1) createPclGeneratorPackbits(pamP, pclGeneratorPP); else createPclGeneratorWholebytes(pamP, pclGeneratorPP); } static void printPages(int const outFd, InputSource * const firstSourceP, enum MediaSize const format, int const dpi, bool const center, float const xoffs, float const yoffs, bool const doDuplex, enum DuplexPageMode const duplex, bool const doCopies, unsigned int const copies, bool const doMediaSource, int const mediaSource, bool const doMediaDestination, int const mediaDestination, bool const colorok) { /*---------------------------------------------------------------------------- Loop over all input files, and each file, all images. -----------------------------------------------------------------------------*/ InputSource * sourceP; unsigned int sourceNum; sourceP = firstSourceP; openDataSource(outFd, eBinaryLowByteFirst, eDefaultSource); sourceNum = 0; /* initial value */ while (sourceP) { FILE * in_file; struct pam pam; bool eof; unsigned int pageNum; in_file = pm_openr(sourceP->name); ++sourceNum; pageNum = 0; /* initial value */ eof = FALSE; while(!eof) { pnm_nextimage(in_file, &eof); if (!eof) { pclGenerator * pclGeneratorP; ++pageNum; pm_message("Processing File %u, Page %u", sourceNum, pageNum); pnm_readpaminit(in_file, &pam, PAM_STRUCT_SIZE(tuple_type)); createPclGenerator(&pam, &pclGeneratorP, colorok); convertAndPrintPage( outFd, pclGeneratorP, &pam, format, dpi, center, xoffs, yoffs, doDuplex, duplex, doCopies, copies, doMediaSource, mediaSource, doMediaDestination, mediaDestination); destroyPclGenerator(pclGeneratorP); } } pm_close(in_file); sourceP = sourceP->next; } XL_Operator(outFd, oCloseDataSource); } static void freeSource(InputSource * const firstSourceP) { InputSource * sourceP; sourceP = firstSourceP; while(sourceP) { InputSource * const nextP = sourceP->next; free(sourceP); sourceP = nextP; } } int main(int argc, char *argv[]) { int const outFd = STDOUT_FILENO; struct cmdlineInfo cmdline; /* In case you're wondering why we do direct file descriptor I/O instead of stream (FILE *), it's because Jochen originally wrote this code for an embedded system with diet-libc. Without the stream library, the statically linked binary was only about 5K big. */ pnm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); jobHead(outFd, cmdline.rendergray, cmdline.jobsetup); beginSession(outFd, cmdline.dpi, cmdline.dpi, eInch, FALSE, eBackChAndErrPage); printPages(outFd,cmdline.sourceP, cmdline.format, cmdline.dpi, cmdline.center, cmdline.xoffs, cmdline.yoffs, cmdline.duplexSpec, cmdline.duplex, cmdline.copiesSpec, cmdline.copies, cmdline.feederSpec, cmdline.feeder, cmdline.outtraySpec, cmdline.outtray, cmdline.colorok ); endSession(outFd); jobEnd(outFd); freeSource(cmdline.sourceP); return 0; }