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

document.cc

///
// Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING
///
#include "pptout/widget/usererror.h" /// \todo fix this
#include <algorithm>
#include <stdexcept>
#include <ctime>
#include <fstream>

#include <libxml++/libxml++.h>

#include "document.h"
#include "fileerrors.h"
#include "page.h"
#include "defines.h" // VERSION
#include "textstream.h"
#include "xml2ps/psstream.hh"
#include "util/stringutil.h"
#include "util/filesys.h"
#include "util/warning.h"
#include "util/os.h"
#include "fonts/fontmanager.hh"
#include "ps/pfb2pfa.h"
#include "ps/type42.h"
#include "ps/pdf.h"

SigC::Signal1<void, DocRef> Document::changed_signal;
SigC::Signal1<void, DocRef> Document::size_changed_signal;
SigC::Signal1<void, DocRef> Document::selection_changed_signal;
SigC::Signal1<void, DocRef> Document::streams_changed_signal;

namespace {             // local
  class NameOrder {
  public:
    bool operator() (const TextStream* x, const TextStream* y) const {
      return x && y && (x->get_name() < y->get_name());
    }
  };
}

DocRef Document::create() {
  DocRef tmp(new Document());
  tmp->reference();
  return tmp;
}

// It is very important not to emit signals from the constructors.
Document::Document()
  : first_page_num(1), stream_num(1), doublesided(true), 
    is_template(false), paper_name("A4"),
    orientation(Papers::PORTRAIT), 
    the_template(0)
{
  pages.push_back(new Page(*this));
}

00057 DocRef Document::create(const std::string &filename, bool is_template) {
  DocRef tmp(new Document(filename, is_template));
  tmp->reference();
  return tmp;
}

00063 Document::Document(const std::string &filename, bool is_template_)
  : stream_num(1), is_template(is_template_), the_template(0)
{
  open(expand_path(filename));
}

00069 DocRef Document::create(const std::string &template_file_) {
  DocRef tmp(new Document(template_file_));
  tmp->reference();
  return tmp;
}

00075 Document::Document(const std::string &template_file_)
  : first_page_num(1), stream_num(1), is_template(false), the_template(0)
{
  set_template(template_file_);
}

Document::~Document() {
  debug << "destroying Document" << std::endl;

  // delete the streams before the pages, or the textframes will try
  // to run the typesetter threads from their destructors
  while(!text_streams.empty()) {
    TextStream *text_stream = *text_streams.begin();
    text_streams.pop_front();
    delete text_stream;
  }

  // delete pages
  while(!pages.empty()) {
    Page *page = *pages.begin();
    pages.pop_front();
    delete page;
  }
}

00100 std::string Document::make_up_new_name() {
  while(stream_num < INT_MAX) {
    std::string tmp = "stream" + tostr(stream_num);
    if(!get_text_stream(tmp))
      return tmp;
    // Up the number only afterwards if no match, so if the user creates a
    // stream, renames it, and creates another one, he doesn't lose numbers.
    ++stream_num;
  }
  /// \todo  Try again from 1, in case a stream was deleted or renamed, but
  /// only once, so we don't get stuck in an eternal loop.  Don't start from 1
  /// each time, to avoid confusing the user
  throw std::runtime_error("Out of automatical stream names");
}

int Document::count_selected() const {
  return selection.size();
}

const Document::Selection& Document::selected() const {
  return selection;
}

00123 void Document::select_all(bool select) {
  selection.clear();

  if(select)
    for(PageVec::const_iterator p = pages.begin(); p != pages.end(); ++p)
      for(Group::ChildVec::const_iterator i = (*p)->pbegin(); 
          i != (*p)->pend(); ++i)
        selection.push_back(*i);
  
  selection_changed_signal(self());
}

00135 void Document::select_all_on_page(Page *page, bool select) {
  if(!page)
    return;

  selection.clear();

  if(select)
    for(Group::ChildVec::const_iterator i = page->pbegin(); 
      i != page->pend(); ++i)
      selection.push_back(*i);
  
  selection_changed_signal(self());
}

void Document::deselect(Pagent* obj) {
  Selection::iterator i = find(selection.begin(), selection.end(), obj);
  if(i != selection.end()) {
    selection.erase(i);
    selection_changed_signal(self());
  }
}

void Document::select(Pagent* obj, bool deselect_old) {
  if(deselect_old)
    selection.clear();
  selection.push_back(obj);
  selection_changed_signal(self());
}

void Document::delete_selected() {
  for(Selection::const_iterator i = selection.begin(); 
      i != selection.end(); ++i) 
    {
      Pagent* obj = (*i)->get_parent().ungroup(*i);
      if(!obj)
      throw std::logic_error("Tried to remove pagent from bad parent.");
      delete obj;
    }
  selection.clear();

  selection_changed_signal(self());
}

00178 Document::StreamVec Document::get_text_streams() {
  StreamVec tmp = text_streams;
  tmp.sort(NameOrder());
  return tmp;
}

void Document::rename_text_stream(const std::string &old_name, 
                          const std::string &new_name) {
  TextStream *stream = get_text_stream(old_name);
  if(!stream)
    throw Error::TextStreamName("A stream with the name \"" + old_name
                                + "\" does not exist.");
  if(old_name == new_name)
    return; //not an error
  if(new_name.empty())
    throw Error::TextStreamName("The stream must have a name");
  if(get_text_stream(new_name))
    throw Error::TextStreamName("A stream with the name \"" + new_name
                                + "\" already exists");
  stream->set_name(new_name);
  streams_changed_signal(self());
}

00201 void Document::add_text_stream(TextStream* new_stream) {
  _add_text_stream(new_stream);
  streams_changed_signal(self());
}

void Document::_add_text_stream(TextStream* new_stream) {
  const std::string& name = new_stream->get_name();
  if(name.empty())
    throw Error::TextStreamName("The stream must have a name");
  if(get_text_stream(name))
    throw Error::TextStreamName("A stream with the name \"" + name
                                + "\" already exists");
  text_streams.push_back(new_stream);
}

namespace {
  template<class A>
  struct NameIs {
    std::string _name;
    NameIs(const std::string &name) : _name(name) {}
    bool operator() (A *a) { return a->get_name() == _name; }
  };
}

TextStream* Document::get_text_stream(const std::string &name) {
  StreamVec::iterator i = std::find_if(text_streams.begin(), text_streams.end(),
                                       NameIs<TextStream>(name));
  return i != text_streams.end() ? *i : 0;
}

void Document::remove_text_stream(const std::string &name) {
  StreamVec::iterator i = std::find_if(text_streams.begin(), text_streams.end(),
                                       NameIs<TextStream>(name));
  if(i != text_streams.end()) {
    delete *i;
    text_streams.erase(i);
    streams_changed_signal(self());  
  }
}

unsigned int Document::get_num_of_pages() const {
  return pages.size();
}

00245 void Document::delete_page(int page_num) {
  int num_of_pages = get_num_of_pages();

  if(page_num >= first_page_num 
     && page_num < first_page_num + num_of_pages) {
    int j = first_page_num;
    PageVec::iterator i = pages.begin();
    
    while(j < page_num && j < first_page_num + num_of_pages) {
      i++;
      j++;
    }
    delete *i;
    pages.erase(i);
  }
  else throw Error::InvalidPageNum(page_num);
  
  /// \todo temporary fix to make sure deleted objects 
  /// are not still selected:
  select_all(false); 

  changed_signal(self());
}

00269 Page *Document::new_page(int page_num, Page *original) {
  std::auto_ptr<Page> the_new_page;
  
  if(original) {
    xmlpp::Document tmpdoc;
    tmpdoc.create_root_node("template");
    FileContext context;
    the_new_page.reset
      (new Page(ElementWrap("no-file",
                            *original->save(*tmpdoc.get_root_node(),  context)),
                *this));
    the_new_page->set_name("");  // the name should not be inherited
  } else {
    the_new_page.reset(new Page(*this));
  }
  
  Page *result = the_new_page.get();
  int num_of_pages = get_num_of_pages();
  
  if(num_of_pages == 0) {
    pages.push_front(the_new_page.release());
  } else if(page_num >= first_page_num 
          && page_num <= first_page_num + num_of_pages) {
    int j = first_page_num;
    PageVec::iterator i = pages.begin();
    while(j < page_num) { i++; j++; }
    
    pages.insert(i, the_new_page.release());
  } else {
    throw Error::InvalidPageNum(page_num);
  }
  
  changed_signal(self());
  return result;
}

00305 int Document::get_page_num_of_page(const Page *page) const {
  int j = get_first_page_num();
  for(PageVec::const_iterator i = pages.begin(); i != pages.end(); i++, j++) {
    if(page == *i)
      return j;
  }
  throw Error::InvalidPageNum();
}

00314 Page *Document::get_page(int page_num) {
  int j = first_page_num;
  PageVec::iterator i = pages.begin();
  int num_of_pages = get_num_of_pages();

  if(num_of_pages == 0)
    return 0;

  while(j < page_num && j < first_page_num + num_of_pages - 1) {
    i++;
    j++;
  }

  return *i;
}

00330 Page *Document::get_page(const std::string &page_name) {
  PageVec::iterator i = std::find_if(pages.begin(), pages.end(),
                                     NameIs<Page>(page_name));
  return i != pages.end() ? *i : 0;
}

std::list<std::string> Document::get_template_pages() {
  std::list<std::string> tmp;
  if(the_template) {
    DocRef &t = the_template;
    for(int i = t->get_first_page_num(); 
      i < t->get_first_page_num() + int(t->get_num_of_pages());
      i++)
      tmp.push_back(t->get_page(i)->get_name());
  }
  return tmp;
}

namespace {
  class OpenFailed : public UserError {
  public:
    OpenFailed(const std::string& filename, const std::exception& err)
      : UserError("Failed to open passepartout file " + filename, err)
      {}
  };
}

00357 void Document::open(const std::string &filename) {
  try {
    xmlpp::DomParser tree(filename);
    /// \todo  Check if get_document is guaranteed to not return 0.
    xmlpp::Element *rootnode = tree.get_document()->get_root_node();
    if(rootnode)
      xml_open(ElementWrap(filename, *rootnode));
    else
      throw std::runtime_error("No such file or no rootnode");
  }
  catch(const xmlpp::exception& e) {
    throw OpenFailed(filename, e); /// \todo Signify the "bad xml" level?
  } 
  catch(const std::exception& e) {
    throw OpenFailed(filename, e);
  }
}

void Document::save(const std::string &filename) {
  try {
    std::auto_ptr<xmlpp::Document> tree(xml_save(FileContext(filename)));
    tree->write_to_file_formatted(filename);
  }
  catch (const xmlpp::exception& err) {
    throw UserError("Failed to write " + filename, err);
  }
}

00385 DocRef Document::self() {
  DocRef tmp(this);
  tmp->reference();
  return tmp;
}

00391 void Document::xml_open(const ElementWrap& xml) {
  /// \todo this function is too long and unreadable
  std::string temp_template = xml.get_filename("template");

  if(xml.get_element_name() != "document")
    throw Error::Read("Root node is not <document>");

  //default values if attribute is not encountered:
  doublesided = true; 
  orientation = Papers::PORTRAIT;
  first_page_num = 1;
  paper_name = "A4";

  //read document attributes:
  xmlpp::Element::AttributeList attributes = xml.element().get_attributes();
  for(xmlpp::Element::AttributeList::iterator i = attributes.begin();
      i != attributes.end();
      i++)
    {
      const std::string name = (*i)->get_name();
      if(name == "template"); // handled elsewhere
      else if(name == "doublesided")
      doublesided = to<bool>((*i)->get_value());
      else if(name == "landscape") {
      if(to<bool>((*i)->get_value()))
        orientation = Papers::LANDSCAPE;
      } else if(name == "paper_name") {
      /// \todo Use a proper type that can be converted for the paper type.
      try {
        paper_name = (*i)->get_value();
      } catch (Error::PaperName e) {
        throw Error::Read("There is no paper called \"" + e.name + "\".");
      }
      } else if(name == "first_page_num") {
      first_page_num = to<int>((*i)->get_value());
      } else
      warning << "Unknown attribute \"" << (*i)->get_name()
            << "\" ignored in <document>." << std::endl;
    }

  if(!is_template) // templates can't have templates
    set_template(temp_template);
  // the template overrides anything  explicitly stated in the document

  // read text streams and pages:
  xmlpp::Element::NodeList children = xml.element().get_children();
  for(xmlpp::Node::NodeList::iterator i = children.begin();
      i != children.end();
      i++) {
    if(xmlpp::Element *elem = dynamic_cast<xmlpp::Element*>(*i)) {
      std::string name = elem->get_name();
      if(name == "text_stream") {
      std::auto_ptr<TextStream> stream
        (new TextStream(ElementWrap(xml, *elem)));
      const std::string &name = stream->get_name();
      if(name.empty())
        throw Error::Read("Text stream has no name.");
        
      // template streams override document streams
      // if two streams have the same name, the second will be ignored
      if(!get_text_stream(name))
        _add_text_stream(stream.release()); // no signal

      
      } else if(name == "page") {
      pages.push_back(new Page(ElementWrap(xml, *elem), *this));
      } else
      warning << "Unknown node <" << name
            << "> ignored in <document>" << std::endl;
    }
  }
}

void Document::set_template(const std::string &template_file_) {
  template_file = template_file_;
  if(!template_file.empty()) {
    the_template = create(template_file, true);
    doublesided = the_template->is_doublesided();
    orientation = the_template->get_orientation();
    paper_name = the_template->get_paper_name();
    StreamVec ts = the_template->get_text_streams();
    for(StreamVec::iterator i = ts.begin();
      i != ts.end(); i++)
      {
      try {
        TextStream* tmp = get_text_stream((*i)->get_name());
        // don't emit signal in case this is called from the
        // constructor
        if(!tmp)
          _add_text_stream(new TextStream((*i)->get_name(),
                                  (*i)->get_association(), 
                                  (*i)->get_transform()));
        else { // override
          tmp->set_association((*i)->get_association());
          tmp->set_transform((*i)->get_transform());
        }
      } catch (Error::TextStreamName e) {
        warning << e.what() << std::endl;
      }
      }
  }
}

void Document::set_doublesided(bool ds) {
  doublesided = ds;
  changed_signal(self());
}

void Document::set_first_page_num(int num) {
  first_page_num = num;
  changed_signal(self());
}

void Document::set_orientation(Papers::Orientation _orientation) {
  orientation = _orientation;
  changed_signal(self());
  size_changed_signal(self());
}

void Document::set_paper_name(const std::string &_paper_name) {
  if(papers.sizes.find(_paper_name) == papers.sizes.end())
    throw Error::PaperName(_paper_name);
  paper_name = _paper_name;
  changed_signal(self());
  size_changed_signal(self());
}

xmlpp::Document *Document::xml_save(const FileContext &context) {
  xmlpp::Document *tree = new xmlpp::Document();
  xmlpp::Element *root = tree->create_root_node("document");
  root->set_attribute("paper_name", get_paper_name());

  if(the_template)
    root->set_attribute("template", context.to(template_file));
  root->set_attribute("doublesided", tostr<bool>(is_doublesided()));
  
  root->set_attribute("landscape", 
                  tostr<bool>(get_orientation() == Papers::LANDSCAPE));
  
  root->set_attribute("first_page_num", tostr(get_first_page_num()));

  for(StreamVec::iterator
      i = text_streams.begin(); i != text_streams.end(); i++)
    (*i)->save(*root, context);
  
  for(PageVec::iterator i = pages.begin(); i != pages.end(); i++)
    (*i)->save(*root, context);

  return tree;
}

00542 void Document::print(std::ostream& out, int first_page, int last_page,
                 bool eps, bool include_fonts, bool grayscale) const
{
  const font::FontManager &fm = font::FontManager::instance();
  
  // merge required fonts from all streams
  font::Fonts used_fonts;
  for(StreamVec::const_iterator j = text_streams.begin();
      j != text_streams.end();
      j++) {
    const font::Fonts &fonts = (*j)->get_used_fonts();
    for(font::Fonts::const_iterator i = fonts.begin();
      i != fonts.end();
      i++) {
      used_fonts.insert(*i);
    }
  }

  using std::endl;
  
  time_t the_time = std::time(0);

  if(!(first_page >= first_page_num 
       && first_page <= last_page
       && last_page <= first_page_num + int(get_num_of_pages()) - 1))
    throw Error::Print("Bad page interval");

  // ignore request to print multible-page EPS 
  eps = eps && first_page == last_page;

  int w = int(get_width() + 0.5);
  int h = int(get_height() + 0.5);
  
  if(eps) {
    out << "%!PS-Adobe-3.0 EPSF-3.0\n"
      << "%%BoundingBox: 0 0 " << w << " " << h << '\n';
  } else {
    out << "%!PS-Adobe-3.0\n";
  }

  
  //out << "%%DocumentData: Clean8Bit" << endl
  // <<"%%LanguageLevel: 2"<<endl
  // actually, we don't really know much about the eps files ...
  if(!eps) {
    // eps images should always be upright
    out << "%%Orientation: "
        << (orientation == Papers::PORTRAIT ? "Portrait\n" : "Landscape\n");
  }
  out << "%%Pages: " << get_num_of_pages() << '\n'
      << "%%PageOrder: Ascend\n"
    //      << "%%Title: " << basename(filename) << '\n'
      << "%%CreationDate: " << std::ctime(&the_time)
    // ctime seems to add a newline
      << "%%Creator: Passepartout " << std::string(VERSION)
      << " by Fredrik Arnerup & Rasmus Kaj\n";

  if(true) // perhaps the user is into cloak and dagger stuff ...
    out << "%%For: " << os::fullname()
      << " <" << os::username() << "@" << os::hostname() << ">"
      << " (" << os::machine() << ", " << os::sysname()
      << " " << os::release() << ")\n";

  out << "%%EndComments\n\n"
      << "%%BeginProlog\n";
  xml2ps::PsStream::psProlog(out);
  out << "%%EndProlog\n\n"
      << "%%BeginSetup" << endl;
  
  // Resource comments
  int line = 0;
  for(font::Fonts::const_iterator i = used_fonts.begin();
      i != used_fonts.end();
      i++) {
    out << (line++ ? "%%+ "
          : (include_fonts 
             ? "%%DocumentSuppliedResources: " 
             : "%%DocumentNeededResources: "))
      << "font " << fm.unalias(*i) << std::endl;
  }

  // %%IncludeResource comments
  if(!include_fonts) {
    for(font::Fonts::const_iterator i = used_fonts.begin();
      i != used_fonts.end();
      i++) {
      out << "%%IncludeResource: font " << fm.unalias(*i) << std::endl;
    }
  } else { // include fonts
    for(font::Fonts::const_iterator i = used_fonts.begin();
      i != used_fonts.end();
      i++) {
      std::string fontfile = fm.getFontFile(*i);
      if(fontfile.empty()) {
      warning << "Couldn't find font file for " << *i << std::endl;
      continue;
      }
      std::ifstream in(fontfile.c_str());
      if(!in) {
      warning << "Couldn't read " << fontfile << std::endl;
      continue;
      }
      out << "%%BeginResource: font " << fm.unalias(*i) << std::endl;
      std::string ext = suffix(fontfile);
      if(ext == "pfa") { // ascii
      out << in.rdbuf();
      
      } else if(ext == "pfb") {
      try {
        PS::pfb2pfa(in, out);
      } catch(const std::runtime_error &e) {
        warning << "error in " << fontfile 
              << " : " << e.what() << std::endl;
      }

      } else if(ext == "ttf") {
        PS::truetype2type42(fontfile, out);

      } else {
        out << "% bad fontfile: " << fontfile << std::endl;
        warning << "unknown font format \"" << ext << "\" in "
                << fontfile << std::endl;
      }
      out << "%%EndResource" << std::endl;
    }
  }

  out << "%%EndSetup" << endl
      << endl;
  
  int page_num = first_page_num;
  for(PageVec::const_iterator i = pages.begin(); i != pages.end(); i++) {
    if(page_num >= first_page && page_num <= last_page) {
      out << endl << "%%Page: \""
        <<(*i)->get_name()<<"\" "<<page_num<<endl;

      if(!eps && orientation == Papers::LANDSCAPE)
        out << "90 rotate 0 " << -h << " translate\n";

      // If we have a bounding box, we may not draw outside of it.
      // Shouldn't really be a problem, but one never knows ...
      if(eps)
      out << "\ngsave\n"
          << "0 0 moveto "            << w << " 0 rlineto\n"
          << "0 " << h <<" rlineto "  << -w << " 0 rlineto\n"
          << "closepath clip newpath\n\n";
      //clip doesn't make implicit newpath

      (*i)->print(out, grayscale);

      if(eps)
      out << "\ngrestore\n";
    }
    page_num++;
  }
  out << "%%EOF" << endl;
}

void Document::print_pdf(PDF::Document::Ptr pdf,
                         int first_page, int last_page) {
  for(StreamVec::iterator
      i = text_streams.begin(); i != text_streams.end(); i++)
    (*i)->print_pdf(pdf);
  
  
  int page_num = first_page_num;
  for(PageVec::const_iterator i = pages.begin(); i != pages.end();
      ++i, ++page_num) {
    if(page_num >= first_page && page_num <= last_page) {
      (*i)->print_pdf(pdf);
    }
  }
}

00716 Document& Document::containing(Pagent& obj) {
  try {
    Page& page = Page::containing(obj);
    return page.document;
    
  } catch(const Error::NoParent& err) {
    throw std::logic_error
      ("Tried to get Document containing pagent that was not in a Document.");
  }
}

00727 const Document& Document::containing(const Pagent& obj) {
  try {
    const Page& page = Page::containing(obj);
    return page.document;
    
  } catch(const Error::NoParent& err) {
    throw std::logic_error
      ("Tried to get Document containing pagent that was not in a Document.");
  }
}

Generated by  Doxygen 1.6.0   Back to index