Logo Search packages:      
Sourcecode: passepartout version File versions  Download package

imageframe.cc

///
// Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING
///
#include "imageframe.h"
#include "fileerrors.h"
#include <sstream>
#include <fstream>
#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(slot(*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(slot(*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>()) {
      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::Ref>()) {
      PDF::Object::Ptr obj = sdoc.getObject(ref);
      // Todo: Array
      if(PDF::Dictionary::Ptr d = obj.dyn_cast<PDF::Dictionary>()) {
      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<PDF::Dictionary>()) {
      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<std::string> 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>();
    
    PDF::Dictionary::Ptr pages = pdfp.getObject(root->get_entry("Pages"))
      .dyn_cast<PDF::Dictionary>();
    
    if(int(pages->get_entry("Count").dyn_cast<PDF::Real>()->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<PDF::Array>()->at(0))
      .dyn_cast<PDF::Dictionary>();
  
    PDF::Dictionary::Ptr content = pdfp.getObject(page->get_entry("Contents"))
      .dyn_cast<PDF::Dictionary>();
    
#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<PDF::Dictionary>()));
    
#else    
    PDF::Array::Ptr box = page.get_entry("MediaBox").dyn_cast<PDF::Array>();
    
    // This is the actual XObject that we create for the output!
    PDF::Content::Ptr xobj = new PDF::Content
      (pdf.getDocument(), 
       int(dynamic_cast<const PDF::Real&>(*box.at(2)).get_value()+0.5),
       int(dynamic_cast<const PDF::Real&>(*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<const PDF::Stream&>(content).rawdata();
  
    // Deep copy of the resources
    PDF::Dictionary *xres = dynamic_cast<PDF::Dictionary*>
      (xobj->get_entry("Resources"));
    if(!xres)
      throw std::runtime_error("Target Resources dictionary missing");
  
    const PDF::Dictionary *sres = dynamic_cast<const PDF::Dictionary*>
      (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(this);
}

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<filedesc::OutBuf&>(*psstream.rdbuf()).close();
}

void ImageFrame::on_file_modified() {
  read_size();
  object_changed_signal(this);
}

// 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);
}

Generated by  Doxygen 1.6.0   Back to index