/// // Copyright (C) 2003, 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "postscriptviewent.h" #include "config.h" #include "document/basicframe.h" // For Gen_Pic_Error; Todo: remove! #include "util/tempfile.h" #include "util/processman.h" #include "util/os.h" #include "util/filesys.h" #include "util/warning.h" #include "util/stringutil.h" // tostr #include "fonts/fontmanager.hh" // getFontPaths #include "widget/usererror.h" #include /// \todo Remote the following line! typedef BasicFrame::GenPicError GenPicError; namespace { enum SpecialPid { P_SHUTDOWN = -3, P_STARTUP = -2, P_IDLE = -1 }; const string& preferredDeviceName() { static string devicename; if(devicename.empty()) { verbose << "Deciding which ghostscript device to use ..." << std::endl; /// \todo Get a "required" attribute from the configuration! const std::string &psinterpreter = config.PSInterpreter.values.front(); if(psinterpreter.empty()) throw UserError("No postscript interpreter specified.", "You probably have a configuration file without the" " PSInterpreter property set."); if(ProcessManager::instance() .system(psinterpreter + " -q -dBATCH -sDEVICE=pngalpha") == 0) devicename = "pngalpha"; else devicename = "ppmraw"; verbose << "... using the " << devicename << " device." << std::endl; } return devicename; } } PostscriptViewent::PostscriptViewent(View& view, const FrameRef psframe, int _scale_factor) : Viewent(view, psframe), frame(psframe), pid(P_IDLE), tmp_file(Tempfile::find_new_name()), scale_factor(_scale_factor), redraw_requested(false) { if(preferredDeviceName() == "pngalpha") scale_factor = 1; ProcessManager::instance().process_stopped.connect (sigc::mem_fun(*this, &PostscriptViewent::process_stopped)); /// \todo Only regenerate when necessary, i.e. not when just moving ... frame->object_changed_signal.connect (sigc::mem_fun(*this, &PostscriptViewent::regenerate)); view.connect_zoom_change (sigc::hide(sigc::mem_fun(*this, &PostscriptViewent::regenerate))); canvas_pixbuf.reset(new Gnome::Canvas::Pixbuf(*content_group)); canvas_pixbuf->property_anchor() = Gtk::ANCHOR_SOUTH_WEST; canvas_pixbuf->property_x() = 0; canvas_pixbuf->property_y() = 0; canvas_pixbuf->property_width_set() = true; canvas_pixbuf->property_height_set() = true; canvas_pixbuf->property_width_in_pixels() = false; canvas_pixbuf->property_height_in_pixels() = false; on_properties_changed(); on_geometry_changed(); regenerate(); } PostscriptViewent::~PostscriptViewent() { redraw_requested = false; if(pid == P_STARTUP) { cerr << "PostscriptViewent terminating while in startup. Bad." << std::endl; } else if(pid > P_IDLE) { stop_process(); } try { unlink(tmp_file); } catch(std::exception& err) { warning << "Failed to delete postscript view render file." << err.what() << std::endl; } } void PostscriptViewent::set_state(State state) { Viewent::set_state(state); // make sure old images don't linger if(state == BROKEN || state == MISSING) { canvas_pixbuf->property_pixbuf() = Glib::RefPtr(); } } void PostscriptViewent::on_geometry_changed() { Viewent::on_geometry_changed(); Vector size = frame->get_inherent_size(); canvas_pixbuf->property_width() = size.x; canvas_pixbuf->property_height() = size.y; } void PostscriptViewent::regenerate() { if(pid != P_IDLE) { //verbose << "PostscriptViewent allready running gs " << pid << std::endl; redraw_requested = true; return; } else { pid = P_STARTUP; Glib::signal_timeout().connect (sigc::mem_fun(*this, &PostscriptViewent::regenerate_bg), 200, Glib::PRIORITY_DEFAULT_IDLE); } } /** This method is run as a separate thread by regenerate(). */ bool PostscriptViewent::regenerate_bg() { try { set_state(WAIT); redraw_requested = false; /// \todo Get a "required" attribute from the configuration! const std::string &psinterpreter = config.PSInterpreter.values.front(); if(psinterpreter.empty()) throw UserError("No postscript interpreter specified.", "You probably have a configuration file without the" " PSInterpreter property set."); const Matrix &m = frame->get_matrix(); const Vector framesize = frame->get_inherent_size(); // Size of frame as pixels in this view. const Gdk::Point pv(int(view.pt2scr(framesize.x * m.sc_x())), int(view.pt2scr(framesize.y * m.sc_y()))); if(pv.get_x() == 0 || pv.get_y() == 0) throw GenPicError(BasicFrame::ZEROSIZE, "Picture has zero size"); std::ostringstream tmp; tmp << psinterpreter //assuming it is ghostscript << " -q -dSAFER -dNOPAUSE -dBATCH" << " -sDEVICE=" << preferredDeviceName(); // gs doesn't use fontconfig, so give font paths as parameters using font::FontManager; const FontManager::FontPaths &fontpaths = FontManager::instance().getFontPaths(); if(!fontpaths.empty()) { tmp << " -sFONTPATH="; FontManager::FontPaths::const_iterator i; for(i = fontpaths.begin(); i != fontpaths.end(); i++) { if(i != fontpaths.begin()) tmp << ":"; // colon is path separator for unix tmp << *i; } } tmp << " -r" << view.get_scrres() * scale_factor << " -g" << scale_factor * pv.get_x() << 'x' << scale_factor * pv.get_y() << " -sOutputFile=" << tmp_file << " -"; Process proc = ProcessManager::instance().run(tmp.str()); pid = proc->get_pid(); verbose << pid << ": " << tmp.str() << std::endl; frame->generate_picture(proc->get_cin()); } catch(const GenPicError &e) { stop_process(); if(e.type == BasicFrame::NOTREADY) redraw_requested = true; // simply try again else { set_state(e.type == BasicFrame::ASSOCIATION ? MISSING : BROKEN); warning << "PostScriptviewent for " << frame->get_name() << ": GenPicError: " << e.what() << std::endl; } pid = P_IDLE; } catch(const std::exception& e) { stop_process(); warning << "PostScriptviewent for " << frame->get_name() << ": " << e.what() << std::endl; set_state(BROKEN); pid = P_IDLE; } catch(...) { stop_process(); warning << "PostScriptviewent for" << frame->get_name() << ": Unknown error" << std::endl; set_state(BROKEN); pid = P_IDLE; } // Try again if needed return (pid == P_IDLE && redraw_requested); } void PostscriptViewent::process_stopped(pid_t _pid, bool exited_normally, int exit_code) { if(_pid != pid) return; try { debug << _pid << ", exit code: " << exit_code << std::endl; if(!exited_normally) throw std::runtime_error("Process " + tostr(_pid) + " exited abnormally"); if(!exists(tmp_file)) throw std::runtime_error("\"" + tmp_file + "\" does not exist"); PixbufRef pixbuf = Gdk::Pixbuf::create_from_file(tmp_file); // Make all white pixels transparent. if(config.FakeTransparency.values.front() && pixbuf && !pixbuf->get_has_alpha()) pixbuf = pixbuf->add_alpha(true, 255, 255, 255); // had some trouble with some images becoming totally transparent // scale if(scale_factor != 1) pixbuf = pixbuf->scale_simple(pixbuf->get_width() / scale_factor, pixbuf->get_height() / scale_factor, Gdk::INTERP_BILINEAR); canvas_pixbuf->property_pixbuf() = pixbuf; on_geometry_changed(); set_state(NORMAL); } catch(const std::exception& e) { set_state(BROKEN); warning << frame->get_name() << ": " << e.what() << std::endl; } catch(const Glib::Error& e) { set_state(BROKEN); warning << frame->get_name() << ": " << e.what() << std::endl; } pid = P_IDLE; if(redraw_requested) regenerate(); } void PostscriptViewent::stop_process() { if(pid > 0) { // We don't want the process termination hook to run. pid_t stopping = pid; pid = P_SHUTDOWN; ProcessManager::instance().stop(stopping); } else { warning << frame->get_name() << ": " << " no process to stop" << std::endl; } }