/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "imageframe.h" #include "fileerrors.h" #include #include #include "util/warning.h" #include "util/stringutil.h" #include "util/filesys.h" #include "util/tempfile.h" #include "util/processman.h" #include "util/rectboundary.h" #include "util/xmlwrap.h" #include "util/filedescriptors.h" #include "ps/misc.h" #include "ps/wineps.h" #include "ps/pdfparser.h" bool ImageFrame::is_postscript(std::string filename) { std::ifstream in(filename.c_str()); char magic[5]; // the magic number is four bytes long return in && in.get(magic, 5) && (std::string(magic) == "%!PS" || PS::check_windows_magic(magic)); } ImageFrame::ImageFrame(Group *parent, const std::string& assoc) : CachedFrame(parent, "Image " + basename(assoc)), association(assoc), filewatcher(association) { filewatcher.modified_signal.connect (sigc::mem_fun(*this, &ImageFrame::on_file_modified)); read_size(); } ImageFrame::ImageFrame(const ElementWrap& xml, Group *parent) : CachedFrame(xml, parent), association(xml.get_filename("file")), filewatcher(association) { filewatcher.modified_signal.connect (sigc::mem_fun(*this, &ImageFrame::on_file_modified)); if(association.empty()) { warning << "No or empty \"file\" attribute found in image frame" << std::endl; } read_size(); // try to read bounding box from file } ImageFrame::~ImageFrame() {} std::string ImageFrame::getTypeName() const { return "image"; } xmlpp::Element *ImageFrame::save(xmlpp::Element& parent_node, const FileContext &context) const { xmlpp::Element *node = CachedFrame::save(parent_node, context); node->set_attribute("type", "image"); node->set_attribute("file", context.to(association)); return node; } void ImageFrame::print(std::ostream &out, bool grayscale) const { if(association.empty()) { CachedFrame::print(out); return; } out << "save\n" << "/showpage {} def\n" << PS::Concat(get_matrix()) << "0 setgray 0 setlinecap 1 setlinewidth\n" << "0 setlinejoin 10 setmiterlimit [] 0 setdash newpath\n"; if(true) //FIXME!!! if(level2) out << "false setoverprint false setstrokeadjust\n"; // redefine color operators // NOTE: does not work for images // NOTE: this does not work for CIE-based or special color spaces if(grayscale) { out << "/setrgbcolor {setrgbcolor currentgray setgray} bind def\n" << "/sethsbcolor {sethsbcolor currentgray setgray} bind def\n" << "/setcmykcolor {setcmykcolor currentgray setgray} bind def\n" << "/setcolor {setcolor currentgray setgray} bind def\n"; } out << "%%BeginDocument: " << association << '\n'; PS::WinEPSFilter in(association.c_str()); if(in) { try { float x1, y1; // width and height of actual file. bool boundingbox_found = false; bool transf_performed = false; std::string tmpline; while(getline(in, tmpline)) { if(!transf_performed && starts_with(tmpline, "%%BoundingBox:")) { std::istringstream tmp(tmpline.c_str()); std::string word; float x2, y2; if(tmp >> word >> x1 >> y1 >> x2 >> y2) { boundingbox_found = true; out << "%%BoundingBox: 0 0 " << x2 - x1 << ' ' << y2 - y1 << '\n'; } } else if(!transf_performed && !starts_with(tmpline, '%')) { out << tmpline << "\n\n"; transf_performed = true; if(!boundingbox_found) throw BasicFrame::GenPicError(BasicFrame::GENERATION, "No BoundingBox found"); else { out << -x1 << ' ' << -y1 << " translate\n\n"; } } else out << tmpline << '\n'; } } catch(GenPicError e){ throw Error::Print(get_name() + ": " + e.what()); } } else throw Error::Print(get_name() + ": Couldn't read " + association); out << "%%EndDocument\n" << "restore" << std::endl; } /// \todo: this should recurse on dictionaries and arrays. PDF::Dictionary::Ptr deepCopy(PDFParser& sdoc, PDF::Document::Ptr ddoc, PDF::Dictionary::Ptr source, PDF::Dictionary::Ptr dest = PDF::Dictionary::Ptr()) { if(!dest) { if(PDF::Stream::Ptr s = source.dyn_cast()) { PDF::Stream::Ptr sdest = PDF::Stream::create(); sdest->data() << s->rawdata(); dest = sdest; } else dest = PDF::Dictionary::create(); } for(PDF::Dictionary::iterator i = source->begin(); i != source->end(); ++i) { if(PDF::Ref::Ptr ref = i->second.dyn_cast()) { PDF::Object::Ptr obj = sdoc.getObject(ref); // Todo: Array if(PDF::Dictionary::Ptr d = obj.dyn_cast()) { obj = deepCopy(sdoc, ddoc, d); } dest->set_entry(i->first, ddoc->get_xrefs()->add_object(obj)); } else if(PDF::Dictionary::Ptr d = i->second.dyn_cast()) { dest->set_entry(i->first, deepCopy(sdoc, ddoc, d)); } else dest->set_entry(i->first, i->second); } return dest; } void ImageFrame::print_pdf(PDF::Content::Ptr pdf) const { try { Tempfile pdfbuf; const Vector framesize = get_inherent_size(); std::vector argv; argv.push_back("gs"); argv.push_back("-q"); argv.push_back("-dSAFER"); argv.push_back("-dNOPAUSE"); argv.push_back("-dBATCH"); argv.push_back("-sDEVICE=pdfwrite"); argv.push_back("-r72"); argv.push_back("-g" + tostr(framesize.x) + "x" + tostr(framesize.y)); /// \todo Tell gs about the fontpath, so we get inclusion of fonts that /// isn't included in the eps. argv.push_back("-sOutputFile=" + pdfbuf.get_filename()); argv.push_back("-"); Process proc = ProcessManager::instance().run(argv); // Just like generate_picture, but without applying the matrix // (that is done when calling the xobject) /// \todo genereate_picture should ingore the matrix, then it could be /// used here. proc->get_cin() << "save\n" << "/showpage {} def\n" << -lowerleft.x << ' ' << -lowerleft.y << " translate\n" << '(' << association << ") run\n" << "restore\n" << "showpage" << std::endl; proc->close_cin(); int retval = proc->wait(); if(retval != 0) { throw std::runtime_error("Failed to convert eps to pdf, gs returned " + tostr(retval)); } verbose << "PDF printing of EPS " << association << "; wrote " << pdfbuf.get_filename() << ", " << retval << std::endl; PDFParser pdfp(pdfbuf.get_filename().c_str()); PDF::Dictionary::Ptr trailer = pdfp.getTrailer(); PDF::Dictionary::Ptr root = pdfp.getObject(trailer->get_entry("Root")) .dyn_cast(); PDF::Dictionary::Ptr pages = pdfp.getObject(root->get_entry("Pages")) .dyn_cast(); if(int(pages->get_entry("Count").dyn_cast()->get_value()) != 1) throw std::runtime_error("Number of pages must be one in eps"); PDF::Dictionary::Ptr page = pdfp.getObject (pages->get_entry("Kids").dyn_cast()->at(0)) .dyn_cast(); PDF::Dictionary::Ptr content = pdfp.getObject(page->get_entry("Contents")) .dyn_cast(); #if 1 PDF::Dictionary::Ptr xobj = deepCopy(pdfp, pdf->getDocument(), content); xobj->set_entry_name("Type", "XObject"); xobj->set_entry_name("Subtype", "Form"); xobj->set_entry("BBox", page->get_entry("MediaBox")); xobj->set_entry("Resources", deepCopy (pdfp, pdf->getDocument(), pdfp.getObject(page->get_entry("Resources")) .dyn_cast())); #else PDF::Array::Ptr box = page.get_entry("MediaBox").dyn_cast(); // This is the actual XObject that we create for the output! PDF::Content::Ptr xobj = new PDF::Content (pdf.getDocument(), int(dynamic_cast(*box.at(2)).get_value()+0.5), int(dynamic_cast(*box.at(3)).get_value()+0.5)); for(PDF::Dictionary::const_iterator i = content.begin(); i != content.end(); ++i) xobj->set_entry(i->first, i->second); xobj->data() << dynamic_cast(content).rawdata(); // Deep copy of the resources PDF::Dictionary *xres = dynamic_cast (xobj->get_entry("Resources")); if(!xres) throw std::runtime_error("Target Resources dictionary missing"); const PDF::Dictionary *sres = dynamic_cast (page.get_entry("Resources")); if(!sres) throw std::runtime_error("Source Resources dictionary missing"); deepCopy(pdfp, pdf.getDocument(), *sres, xres); #endif // And at last -- actually register and call the XObject const std::string objname = pdf->registerXObj(xobj); pdf->data() << "q\n" << get_matrix() << " cm\n" << '/' << objname << " Do\n" << "Q\n"; } catch(const std::exception& err) { throw std::runtime_error("Failed to print eps \"" + association + "\" to pdf: " + err.what()); } } void ImageFrame::set_association(const std::string &s) { if(association == s) return; association = s; filewatcher.set_file(association); on_file_modified(); props_changed_signal(); } Boundary ImageFrame::get_box() const { return RectBoundary::create(get_matrix(), inherentsize.x, inherentsize.y); } void ImageFrame::generate_picture(std::ostream& psstream) const { if(association.empty()) throw GenPicError(ASSOCIATION, "No associated file"); if(!access(association)) throw GenPicError(ASSOCIATION, "Can't read " + association); const Matrix &m = get_matrix(); // We don't have to write the image data to gs, just tell it where it's at. // The save/restore/showpage stuff is so there is one and only one effective // showpage sent to gs. psstream << "save\n" << "/showpage {} def\n" << m.sc_x() << ' ' << m.sc_y() << " scale\n" << -lowerleft.x << ' ' << -lowerleft.y << " translate\n" << '(' << association << ") run\n" << "restore\n" << "showpage" << std::endl; // Ugly, but the best way I found to actually close the stream: dynamic_cast(*psstream.rdbuf()).close(); } void ImageFrame::on_file_modified() { read_size(); object_changed_signal(); } // Note: as long as this is only called in the constructor there is no need to // emit a geometry_changed_signal void ImageFrame::read_size() { if(!association.empty()) { PS::WinEPSFilter in(association); std::string tmpline; while(getline(in, tmpline)) { if(starts_with(tmpline, "%%BoundingBox:")) { std::istringstream tmp(tmpline.c_str()); std::string word; float x1, y1, x2, y2; if(tmp >> word >> x1 >> y1 >> x2 >> y2) { lowerleft = Vector(x1, y1); inherentsize = Vector(x2 - x1, y2 - y1); return; } else warning << "Bad bounding box in " << association << std::endl; } } } // If we din't find a value in the file, set som arbitrary values. lowerleft = Vector(0, 0); inherentsize = Vector(100, 100); }