/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * @brief Decode URF to a PDF file * @file urftopdf.cpp * @author Neil 'Superna' Armstrong (C) 2010 * @author Tobias Hoffmann (c) 2012 */ #include #include #include #include #include #include #include #include #include #include #include // ntohl #include #include #include #include #include #include #include "unirast.h" #define DEFAULT_PDF_UNIT 72 // 1/72 inch #define PROGRAM "urftopdf" #ifdef URF_DEBUG #define dprintf(format, ...) fprintf(stderr, "DEBUG: (" PROGRAM ") " format, __VA_ARGS__) #else #define dprintf(format, ...) #endif #define iprintf(format, ...) fprintf(stderr, "INFO: (" PROGRAM ") " format, __VA_ARGS__) void die(const char * str) { fprintf(stderr, "CRIT: (" PROGRAM ") die(%s) [%s]\n", str, strerror(errno)); exit(1); } //------------- PDF --------------- struct pdf_info { pdf_info() : pagecount(0), width(0),height(0), pixel_bytes(0),line_bytes(0), bpp(0), page_width(0),page_height(0) { } QPDF pdf; QPDFObjectHandle page; unsigned pagecount; unsigned width; unsigned height; unsigned pixel_bytes; unsigned line_bytes; unsigned bpp; PointerHolder page_data; double page_width,page_height; }; int create_pdf_file(struct pdf_info * info, unsigned pagecount) { try { info->pdf.emptyPDF(); } catch (...) { return 1; } info->pagecount = pagecount; return 0; } QPDFObjectHandle makeBox(double x1, double y1, double x2, double y2) { QPDFObjectHandle ret=QPDFObjectHandle::newArray(); ret.appendItem(QPDFObjectHandle::newReal(x1)); ret.appendItem(QPDFObjectHandle::newReal(y1)); ret.appendItem(QPDFObjectHandle::newReal(x2)); ret.appendItem(QPDFObjectHandle::newReal(y2)); return ret; } enum ColorSpace { DEVICE_GRAY, DEVICE_RGB, DEVICE_CMYK }; #define PRE_COMPRESS /* or temporarily store images? if(cupsTempFile2(tempfile_name, 255) == NULL) die("Unable to create a temporary pdf file"); iprintf("Created temporary file '%s'\n", tempfile_name); */ QPDFObjectHandle makeImage(QPDF &pdf, PointerHolder page_data, unsigned width, unsigned height, ColorSpace cs, unsigned bpc) { QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf); std::map dict; dict["/Type"]=QPDFObjectHandle::newName("/XObject"); dict["/Subtype"]=QPDFObjectHandle::newName("/Image"); dict["/Width"]=QPDFObjectHandle::newInteger(width); dict["/Height"]=QPDFObjectHandle::newInteger(height); dict["/BitsPerComponent"]=QPDFObjectHandle::newInteger(bpc); if (cs==DEVICE_GRAY) { dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray"); } else if (cs==DEVICE_RGB) { dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); } else if (cs==DEVICE_CMYK) { dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); } else { return QPDFObjectHandle(); } ret.replaceDict(QPDFObjectHandle::newDictionary(dict)); #ifdef PRE_COMPRESS // we deliver already compressed content (instead of letting QPDFWriter do it), to avoid using excessive memory Pl_Buffer psink("psink"); Pl_Flate pflate("pflate",&psink,Pl_Flate::a_deflate); pflate.write(page_data->getBuffer(),page_data->getSize()); pflate.finish(); // /Filter /FlateDecode // /DecodeParms [<>] ?? ret.replaceStreamData(PointerHolder(psink.getBuffer()), QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull()); #else ret.replaceStreamData(page_data,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull()); #endif return ret; } void finish_page(struct pdf_info * info) { //Finish previous Page if(!info->page_data.getPointer()) return; QPDFObjectHandle image = makeImage(info->pdf, info->page_data, info->width, info->height, DEVICE_RGB, 8); if(!image.isInitialized()) die("Unable to load image data"); // add it info->page.getKey("/Resources").getKey("/XObject").replaceKey("/I",image); // draw it std::string content; content.append(QUtil::double_to_string(info->page_width) + " 0 0 " + QUtil::double_to_string(info->page_height) + " 0 0 cm\n"); content.append("/I Do\n"); info->page.getKey("/Contents").replaceStreamData(content,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull()); // bookkeeping info->page_data = PointerHolder(); } int add_pdf_page(struct pdf_info * info, int pagen, unsigned width, unsigned height, int bpp, unsigned dpi) { try { finish_page(info); // any active info->width = width; info->height = height; info->pixel_bytes = bpp/8; info->line_bytes = (width*info->pixel_bytes); info->bpp = bpp; if (info->height > (std::numeric_limits::max() / info->line_bytes)) { die("Page too big"); } info->page_data = PointerHolder(new Buffer(info->line_bytes*info->height)); QPDFObjectHandle page = QPDFObjectHandle::parse( "<<" " /Type /Page" " /Resources <<" " /XObject << >> " " >>" " /MediaBox null " " /Contents null " ">>"); page.replaceKey("/Contents",QPDFObjectHandle::newStream(&info->pdf)); // data will be provided later // Convert to pdf units info->page_width=((double)info->width/dpi)*DEFAULT_PDF_UNIT; info->page_height=((double)info->height/dpi)*DEFAULT_PDF_UNIT; page.replaceKey("/MediaBox",makeBox(0,0,info->page_width,info->page_height)); info->page = info->pdf.makeIndirectObject(page); // we want to keep a reference info->pdf.addPage(info->page, false); } catch (std::bad_alloc &ex) { die("Unable to allocate page data"); } catch (...) { return 1; } return 0; } int close_pdf_file(struct pdf_info * info) { try { finish_page(info); // any active QPDFWriter output(info->pdf,NULL); output.write(); } catch (...) { return 1; } return 0; } void pdf_set_line(struct pdf_info * info, unsigned line_n, uint8_t line[]) { dprintf("pdf_set_line(%d)\n", line_n); if(line_n > info->height) { dprintf("Bad line %d\n", line_n); return; } memcpy((info->page_data->getBuffer()+(line_n*info->line_bytes)), line, info->line_bytes); } // Data are in network endianness struct urf_file_header { char unirast[8]; uint32_t page_count; } __attribute__((__packed__)); struct urf_page_header { uint8_t bpp; uint8_t colorspace; uint8_t duplex; uint8_t quality; uint32_t unknown0; uint32_t unknown1; uint32_t width; uint32_t height; uint32_t dot_per_inch; uint32_t unknown2; uint32_t unknown3; } __attribute__((__packed__)); int decode_raster(int fd, unsigned width, unsigned height, int bpp, struct pdf_info * info) { // We should be at raster start int i, j; unsigned cur_line = 0; unsigned pos = 0; uint8_t line_repeat_byte = 0; unsigned line_repeat = 0; int8_t packbit_code = 0; int pixel_size = (bpp/8); std::vector pixel_container; std::vector line_container; if (width > (std::numeric_limits::max() / pixel_size)) { die("Line too big"); } try { pixel_container.resize(pixel_size); line_container.resize(pixel_size*width); } catch (...) { die("Unable to allocate temporary storage"); } do { if(read(fd, &line_repeat_byte, 1) < 1) { dprintf("l%06d : line_repeat EOF at %lu\n", cur_line, lseek(fd, 0, SEEK_CUR)); return 1; } line_repeat = (unsigned)line_repeat_byte + 1; dprintf("l%06d : next actions for %d lines\n", cur_line, line_repeat); // Start of line pos = 0; do { if(read(fd, &packbit_code, 1) < 1) { dprintf("p%06dl%06d : packbit_code EOF at %lu\n", pos, cur_line, lseek(fd, 0, SEEK_CUR)); return 1; } dprintf("p%06dl%06d: Raster code %02X='%d'.\n", pos, cur_line, (uint8_t)packbit_code, packbit_code); if(packbit_code == -128) { dprintf("\tp%06dl%06d : blank rest of line.\n", pos, cur_line); memset((&line_container[pos*pixel_size]), 0xFF, (pixel_size*(width-pos))); pos = width; break; } else if(packbit_code >= 0 && packbit_code <= 127) { int n = (packbit_code+1); //Read pixel if(read(fd, &pixel_container[0], pixel_size) < pixel_size) { dprintf("p%06dl%06d : pixel repeat EOF at %lu\n", pos, cur_line, lseek(fd, 0, SEEK_CUR)); return 1; } dprintf("\tp%06dl%06d : Repeat pixel '", pos, cur_line); for(j = 0 ; j < pixel_size ; ++j) dprintf("%02X ", pixel_container[j]); dprintf("' for %d times.\n", n); for(i = 0 ; i < n ; ++i) { //for(j = pixel_size-1 ; j >= 0 ; --j) for(j = 0 ; j < pixel_size ; ++j) line_container[pixel_size*pos + j] = pixel_container[j]; ++pos; if(pos >= width) break; } if(i < n && pos >= width) { dprintf("\tp%06dl%06d : Forced end of line for pixel repeat.\n", pos, cur_line); } if(pos >= width) break; } else if(packbit_code > -128 && packbit_code < 0) { int n = (-(int)packbit_code)+1; dprintf("\tp%06dl%06d : Copy %d verbatim pixels.\n", pos, cur_line, n); for(i = 0 ; i < n ; ++i) { if(read(fd, &pixel_container[0], pixel_size) < pixel_size) { dprintf("p%06dl%06d : literal_pixel EOF at %lu\n", pos, cur_line, lseek(fd, 0, SEEK_CUR)); return 1; } //Invert pixels, should be programmable for(j = 0 ; j < pixel_size ; ++j) line_container[pixel_size*pos + j] = pixel_container[j]; ++pos; if(pos >= width) break; } if(i < n && pos >= width) { dprintf("\tp%06dl%06d : Forced end of line for pixel copy.\n", pos, cur_line); } if(pos >= width) break; } } while(pos < width); dprintf("\tl%06d : End Of line, drawing %d times.\n", cur_line, line_repeat); // write lines for(i = 0 ; i < (int)line_repeat ; ++i) { pdf_set_line(info, cur_line, &line_container[0]); ++cur_line; } } while(cur_line < height); return 0; } int main(int argc, char **argv) { int fd, page; struct urf_file_header head, head_orig; struct urf_page_header page_header, page_header_orig; struct pdf_info pdf; FILE * input = NULL; if(argc < 6) { fprintf(stderr, "Usage: %s