/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "textstream.h" #include "getxsltparams.h" #include #include #include "util/processman.h" #include "util/warning.h" #include "util/stringutil.h" #include "util/filesys.h" #include "util/os.h" #include "xml2ps/typesetter.hh" #include "xml2ps/pdfcanvas.hh" #include "textframe.h" #include "page.h" #include "typesetterthread.h" namespace { // local class SortingPredicate { public: bool operator () (const TextFrame& a, const TextFrame& b) { try { int a_num = a.get_page_num(); int b_num = b.get_page_num(); if(a_num < b_num) return true; if(a_num > b_num) return false; } catch(...) { // Error::InvalidPageNum shouldn't happen, but it has happened return true; // something broken; any order! } // Same page, look at boxes. const Vector a_vec = a.get_box()->getCorner(Corner::UL); const Vector b_vec = b.get_box()->getCorner(Corner::UL); static const float k = 0.1; // 10% slope const float m = a_vec.y - k * a_vec.x; return b_vec.y < k * b_vec.x + m; } }; struct SortingPredicatePtr { bool operator () (TextFrame *a, TextFrame *b) { return SortingPredicate()(*a, *b); } }; bool gtk_main_running = false; } TextStream::TextStream(const std::string& _name, const std::string& _association, const std::string& _transform) : name(_name), association(_association), transform(_transform), failed(false), association_watcher(association), transform_watcher(transform), typesetter_thread(0) { typedef std::vector Vec; Vec params = getXsltParams(transform); for(Vec::const_iterator i = params.begin(); i != params.end(); ++i) parameters[*i] = ""; association_watcher.modified_signal.connect (sigc::mem_fun(*this, &TextStream::on_file_modified)); transform_watcher.modified_signal.connect (sigc::mem_fun(*this, &TextStream::on_file_modified)); // make sure Gtk::main() is running Glib::signal_idle().connect(sigc::mem_fun(*this, &TextStream::on_idle)); } TextStream::TextStream(const ElementWrap& xml) : name(xml.get_attribute("name")), association(xml.get_filename("file")), transform(xml.get_filename("transform")), failed(false), association_watcher(association), transform_watcher(transform), typesetter_thread(0) { // read param defs typedef std::vector Vec; Vec params = getXsltParams(transform); for(Vec::const_iterator i = params.begin(); i != params.end(); ++i) parameters[*i] = ""; //read param values xmlpp::Element::NodeList children = xml.element().get_children(); for(xmlpp::Element::NodeList::iterator i = children.begin(); i != children.end(); ++i) { if(xmlpp::Element *pnode = dynamic_cast(*i)) if(pnode->get_name() == "parameter") { set_parameter (pnode->get_attribute("name")->get_value(), pnode->get_attribute("value")->get_value()); } } association_watcher.modified_signal.connect (sigc::mem_fun(*this, &TextStream::on_file_modified)); transform_watcher.modified_signal.connect (sigc::mem_fun(*this, &TextStream::on_file_modified)); // make sure Gtk::main() is running Glib::signal_idle().connect(sigc::mem_fun(*this, &TextStream::on_idle)); } TextStream::~TextStream() { // remove_frame has the property that removing an unexisting frame is not an // error, just a no-op. for(FramePtrs::iterator i = frames.begin(); i != frames.end(); i++) (*i)->set_stream(0, false); // do not let the frame remove itself from this stream } xmlpp::Element *TextStream::save(xmlpp::Element& parent_node, const FileContext &context) const { xmlpp::Element *tmp = parent_node.add_child("text_stream"); tmp->set_attribute("name", name); tmp->set_attribute("file", context.to(association)); tmp->set_attribute("transform", context.to(transform)); for(TextStream::ParamIter j = param_begin(); j != param_end(); ++j) if(!j->second.empty()) { xmlpp::Element *par = tmp->add_child("parameter"); par->set_attribute("name", j->first); par->set_attribute("value", j->second); } return tmp; } void TextStream::on_file_modified() { /// \todo This method should do something that triggers a /// generate_ps_request from any associated TextFrame that actually is /// visible. But for now, just call run_typesetter() directly. run_typesetter(); } void TextStream::add_frame(TextFrame *text_frame) { // Don't insert a duplicate entry if(find(frames.begin(), frames.end(), text_frame) == frames.end()) frames.push_back(text_frame); } void TextStream::remove_frame(TextFrame *text_frame) { FramePtrs::iterator i = find(frames.begin(), frames.end(), text_frame); if(i != frames.end()) frames.erase(i); run_typesetter(); } void TextStream::generate_ps_request(TextFrame *frame) { if(frame) debug << "Request by " << frame->get_name() << std::endl; if(failed) { // the last attempt failed failed = false; // so we can try again throw BasicFrame::GenPicError(BasicFrame::GENERATION, typesetter_error); } run_typesetter(); } void TextStream::run_typesetter() { if(association.empty()) throw BasicFrame::GenPicError(BasicFrame::ASSOCIATION, "No associated file"); if(!access(association)) throw BasicFrame::GenPicError(BasicFrame::ASSOCIATION, "Could not open \"" + association + "\""); if(frames.empty()) return; if(!gtk_main_running) { debug << "Not starting typesetter thread because Gtk::Main isn't running." << std::endl; return; } /// \todo {Tell the old thread to stop and start a new one, this method is /// (should be) called when there is a _new_ change in the source or frame /// formats only. This way we lag behind. /// ... or possibly just accept a little lag, but in that case, make sure a /// new typesetter_thread is started after the last one that was ignored.} // start typesetter in separate thread frames.sort(SortingPredicatePtr()); if(!typesetter_thread || typesetter_thread->is_done()) { TypesetterThread::FrameDataList sorted_frames; for(FramePtrs::const_iterator i = frames.begin(); i != frames.end(); i++) { /// \todo bundling obstacles with frames is inefficient sorted_frames.push_back(TypesetterThread::FrameData (**i, Page::containing(**i).obstacle_list())); (*i)->begin_write_ps(); } typesetter_thread = TypesetterThread::create(association, transform, parameters, sorted_frames); typesetter_thread->signal_done.connect (sigc::mem_fun(this, &TextStream::on_typesetter_done)); } else debug << "Typesetter already working, not starting new." << std::endl; } void TextStream::on_typesetter_done(Glib::ustring error, Glib::RefPtr new_canvas, bool truncated) { typesetter_error = error; frames.sort(SortingPredicatePtr()); if(error.empty()) { used_fonts = new_canvas->getUsedFonts(); // The new canvas is now ready to get data from pageno_map.clear(); canvas = new_canvas; int num = 1; for(FramePtrs::const_iterator i = frames.begin(); i != frames.end(); ++i, ++num) { pageno_map[*i] = num; FramePtrs::const_iterator j = i; (*i)->end_write_ps(true, truncated && (++j == frames.end())); } } else { // error for(FramePtrs::const_iterator i = frames.begin(); i != frames.end(); ++i) (*i)->end_write_ps(false, false, error); failed = true; // report the last attempt to be a failure } } void TextStream::set_association(const std::string &s) { association = s; // perhaps it has been applied, but not for this file try { on_file_modified(); } catch(const BasicFrame::GenPicError& e) { warning << e.what() << std::endl; } association_watcher.set_file(association); } const std::string &TextStream::get_association() const { return association; } void TextStream::set_name(const std::string &s) { name = s; } const std::string &TextStream::get_name() const { return name; } void TextStream::set_transform(const std::string& s) { if(s == transform) return; transform = s; typedef std::vector Vec; Vec params = getXsltParams(transform); for(Vec::const_iterator i = params.begin(); i != params.end(); ++i) parameters[*i] = ""; on_file_modified(); transform_watcher.set_file(transform); } const std::string &TextStream::get_transform() const { return transform; } void TextStream::set_parameter(const std::string& name, const std::string& value) { parameters[name] = value; on_file_modified(); } void TextStream::outputPageRaw(std::ostream& out, const TextFrame* frame) { if(canvas) { canvas->closePage(); canvas->appendPage(pageno_map[frame], out); } else throw std::runtime_error("Missing canvas while running outputPageRaw"); } void TextStream::outputPageEps(std::ostream& out, const TextFrame* frame) { if(canvas) { canvas->closePage(); canvas->pageEps(pageno_map[frame], out); } else throw std::runtime_error("Missing canvas while running outputPageEps"); } void TextStream::print_pdf(PDF::Document::Ptr pdfdoc) { // don't try to print streams with no pages if(frames.empty()) return; debug << "TextStream:p-pdf: a\n"; Process xformproc; // run xsltproc, but only if there is a stylesheet if(!transform.empty()) { xformproc = runXsltProc(transform, association, parameters.begin(), parameters.end()); } debug << "TextStream:p-pdf: c\n"; xml2ps::Canvas::PageVec pages; frames.sort(SortingPredicatePtr()); for(FramePtrs::const_iterator i = frames.begin(); i != frames.end(); i++) { const Vector framesize = (*i)->get_inherent_size(); pages.push_back(xml2ps::PageBoundary((*i)->get_page_num(), framesize.x, framesize.y, (*i)->get_num_columns(), (*i)->get_gutter())); // A boundary for the actual text frame, to make sure it doesn't try to // flow around itself. const Boundary boundary = (*i)->get_obstacle_boundary(); // Transform from page coords to local textframe coords. const Matrix xform((*i)->get_matrix().inv()); // List of boundaries the text should flow around const BoundaryVect obstacle_list = Page::containing(**i).obstacle_list(); for(BoundaryVect::const_iterator j = obstacle_list.begin(); j != obstacle_list.end(); j++) { if(boundary != *j) { pages.back().addObstacle(*j * xform); } } } debug << "TextStream:p-pdf: running internal xml2ps ..." << std::endl; pdfcanvas.reset(new xml2ps::PDFCanvas(pdfdoc, xml2ps::PDFCanvas::XObjs, pages, false /* no extra pages */)); //canvas.setSubstituteFontAliases(true); debug << "TextStream:p-pdf: parser" << std::endl; xml2ps::PsConverter parser(*pdfcanvas); try { debug << "TextStream:p-pdf: parsing" << std::endl; if(xformproc) { parser.parse_stream(xformproc->get_cout()); } else { std::ifstream file(association.c_str()); parser.parse_stream(file); } pdfcanvas->closePage(); } catch(const xml2ps::OutOfPages&) { warning << "content truncated\n"; } debug << "TextStream:p-pdf: done" << std::endl; } PDF::Object::Ptr TextStream::getPagePDF(const TextFrame* frame) { if(pdfcanvas.get()) { return pdfcanvas->getPageObject(pageno_map[frame]); } else throw std::runtime_error("Missing canvas while running getPagePDF"); } // a hack to make sure the gtkmm main loop is running before we // start any threads bool TextStream::on_idle() { if(!gtk_main_running) { debug << "Gtk::Main is running" << std::endl; // if there has been an idle signal, then everything is cool gtk_main_running = true; } if(!association.empty()) run_typesetter(); // disconnect from handler return false; }