/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "pdf.h" #include "pfb2pfa.h" #include "fonts/fontmetrics.hh" #include "fonts/fontmanager.hh" #include "fonts/freetype.hh" /// \todo remove #include "util/filesys.h" #include "util/stringutil.h" #include "util/warning.h" #include #include #include #include #include #include namespace { const char* standard_fonts[] = { "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic", "Helvetica", "Helvetica-BoldOblique", "Helvetica-Oblique", "Helvetica-Bold", "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique", "Symbol", "ZapfDingbats", 0 }; /// \todo move into FontInfo or something enum FontType { TYPE1, TRUETYPE }; } /// \todo move this into FontInfo itself FontType getFontType(const font::FontInfo &fontinfo) { // find out the font type try { dynamic_cast(fontinfo.getMetrics()); return TRUETYPE; } catch(const std::bad_cast&) { } return TYPE1; // the type is type1 unless it is truetype } namespace PDF { std::ostream& operator << (std::ostream& out, Object& obj) { return obj.write(out); } ReferencedObject::ReferencedObject(Ref::Ptr ref, Object::Ptr object) :obj_(object), ref_(ref) {} Ref::Ptr ReferencedObject::get_ref() const { if(!ref_) throw std::runtime_error("get_ref for object with no ref yet"); return ref_; } std::ostream& ReferencedObject::write(std::ostream& out) { out << ref_->get_num() << ' ' << ref_->get_generation() << " obj\n" << obj_ << "endobj\n"; return out; } XRefs::Ptr XRefs::create() { return Ptr(new XRefs); } XRefs::XRefs() {} Ref::Ptr XRefs::add_object(Object::Ptr object) { ReferencedObject::Ptr ro = ReferencedObject::create(Ref::create(objects.size() + 1, 0u), object); objects.push_back(ro); return ro->get_ref(); } std::streampos XRefs::get_xref_offset() { return xref_offset; } Ref::Num XRefs::get_num_of_refs() const { return objects.size(); } Name::Name(const std::string& name_) : name(name_) {} bool Name::operator < (const Name& x) const { return this->name < x.name; } const std::string& Name::get_name() const { return name; } std::ostream& Name::write(std::ostream& out) { out << '/' << name; return out; } template<> std::ostream& String::write(std::ostream& out) { out << '('; out << value_; // Todo: Handle special chars! out << ')'; return out; } Object::Ptr Array::push_back(Object::Ptr object) { items.push_back(object); return object; } std::ostream& Array::write(std::ostream& out) { out << '['; for(Items::const_iterator i = items.begin(); i != items.end(); ++i) out << *i << ' '; out << ']'; return out; } Array::Ptr rectangle(int llx, int lly, int urx, int ury) { Array::Ptr result = Array::create(); result->push_back(Integer::create(llx)); result->push_back(Integer::create(lly)); result->push_back(Integer::create(urx)); result->push_back(Integer::create(ury)); return result; } Dictionary::Dictionary() {} Object::Ptr Dictionary::set_entry(const std::string& name, Object::Ptr object) { entries[name] = object; return object; } Object::Ptr Dictionary::set_entry_name(const std::string& name, const std::string& value){ return set_entry(name, Name::create(value)); } Object::Ptr Dictionary::set_entry_int(const std::string& name, int value) { return set_entry(name, Integer::create(value)); } const Object::Ptr Dictionary::get_entry(const std::string& name) const { Entries::const_iterator i = entries.find(name); if(i != entries.end()) return i->second; else return Object::Ptr(); } Object::Ptr Dictionary::get_entry(const std::string& name) { Entries::iterator i = entries.find(name); if(i != entries.end()) return i->second; else return Object::Ptr(); } std::ostream& Dictionary::write(std::ostream& out) { out << "<<\n"; for(Entries::const_iterator i = entries.begin(); i != entries.end(); i++) { out << '/' << i->first << ' ' << i->second << '\n'; } out << ">>\n"; return out; } class Resources : public Dictionary { public: typedef RefCountPtr Ptr; enum Id { FONTS, XOBJS, N }; static Ptr create() { return Ptr(new Resources); } Dictionary::Ptr get(Id id, const std::string& name) { if(!resource[id]) { resource[id] = Dictionary::create(); set_entry(name, resource[id]); } return resource[id]; } std::string registerXObj(Ref::Ptr object) { Dictionary::Ptr res = get(XOBJS, "XObject"); const std::string name = "X" + tostr(res->size()); res->set_entry(name, object); return name; } const std::string& registerFont(PDF::Document::FontWithName fwn) { Dictionary::Ptr res = get(Resources::FONTS, "Font"); res->set_entry(fwn.second, fwn.first); return fwn.second; } protected: Resources() { resource[FONTS] = Dictionary::Ptr(); resource[XOBJS] = Dictionary::Ptr(); } private: Dictionary::Ptr resource[N]; }; Stream::Stream() { // Scientific notation is not allowed in PDF. /// \note Here I set fixed, but I actually don't want that. Trailing zero /// decimals should be truncated ... data_.setf(std::ios_base::fixed, std::ios_base::floatfield); } std::ostream& Stream::write(std::ostream& out) { const std::string streamdata = data_.str(); set_entry_int("Length", streamdata.length()); Dictionary::write(out); out << "stream\n" << streamdata << "\nendstream\n"; return out; } std::string Stream::rawdata() const { return data_.str(); } class Page: public Dictionary { public: typedef RefCountPtr Ptr; static Ptr create(int width, int height, Ref::Ptr parent, Document::Ptr document); Content::Ptr get_content(); /** * Make sure a font is loaded in the document, return the font resource * name for this page. */ std::string getFont(const font::FontInfo& font); /** * Register an object for use as an XObject. The object is * given a name in the resource dictionary for XObjects. * \param object the object * \return the XObject name of the object */ std::string registerXObj(Object::Ptr object); void set_parent(Ref::Ptr ref); Document::Ptr document() { return document_; } Resources::Ptr resources() { return resources_; } protected: Page(int width, int height, Ref::Ptr parent, Document::Ptr document); private: Document::Ptr document_; Content::Ptr content; Resources::Ptr resources_; int num_reffed_fonts; }; Content::Ptr Content::create(RefCountPtr page) { return Ptr(new Content(page->document(), page->resources())); } Content::Ptr Content::create(DocumentPtr document, int width, int height) { ResourcesPtr resources = Resources::create(); Ptr result(new Content(document, resources)); result->set_entry_name("Type", "XObject"); result->set_entry_name("Subtype", "Form"); //set_entry_int("FormType", 1); /// \note the 20 point margin is rather arbitrary - it's enough /// for the inital in Emissionen :-) result->set_entry("BBox", rectangle(-20, -20, width + 20, height + 20)); result->set_entry("Resources", resources); return result; } Content::Content(Document::Ptr doc, Resources::Ptr res) : document_(doc), resources(res), last_xpos(0), last_ypos(0), cur_charspace(0), cur_wordspace(0), cur_size(1) {} std::ostream& Content::write(std::ostream& out) { return Stream::write(out); } std::string Content::registerXObj(Object::Ptr object) { return resources->registerXObj(document_->get_xrefs()->add_object(object)); } void Content::beginText() { data() << "BT\n"; } void Content::endText() { commitText(); data() << "ET\n"; } void Content::selectfont(const font::FontInfo& font) { commitText(); cur_size = font.getSize(); data() << '/' << resources->registerFont(document_->getFontObject(font)) << ' ' << cur_size << " Tf\n"; // Make sure show() and friends know whether the current font is // multi-byte or not simple_font = getFontType(font) != TRUETYPE; } void Content::setgray(float gray) { commitText(); data() << gray << " g\n"; } void Content::moveto(float xpos, float ypos) { commitText(); data() << (xpos-last_xpos) << ' ' << (ypos-last_ypos) << " Td\n"; last_xpos=xpos; last_ypos=ypos; } void Content::textRise(float rise) { commitText(); data() << rise << " Ts\n"; } void Content::setWordSpace(const float& w) { if(w != cur_wordspace) { commitText(); cur_wordspace = w; if(simple_font) // Tw seems to want the space scaled by font size data() << w * cur_size << " Tw\n"; } } void Content::setCharSpace(const float& w) { if(w != cur_charspace) { commitText(); cur_charspace = w; // Tc seems to want the space scaled by font size data() << w * cur_size << " Tc\n"; } } void Content::show(const Glib::ustring& s) { // double byte chars for CID if(!simple_font) { using namespace std; textbuf << " <"; for(Glib::ustring::const_iterator i = s.begin(); i != s.end(); i++) textbuf << hex << setw(4) << setfill('0') << int(*i); textbuf << dec << '>'; return; } std::ostringstream encoded; // MacRoman seems to go by different names in different // installations ... /// \todo ugly hack bool macromanexists = true; try { Glib::IConv("MacRoman", "UTF-8"); } catch(...) { macromanexists = false; } Glib::IConv macroman(macromanexists ? "MacRoman" : "Mac", "UTF-8"); // In PDF, backslashes and parentheses need to be escaped for(Glib::ustring::const_iterator i = s.begin(); i != s.end(); ++i) { switch(*i) { case '\\': encoded << "\\\\"; break; case '(': encoded << "\\("; break; case ')': encoded << "\\)"; break; default: try { Glib::ustring t(1, *i); encoded << macroman.convert(t.raw()); } catch(const Glib::ConvertError &e) { document_->convert_failures++; verbose << e.what() << std::endl; // PDF doesn't seem to have anything like glyphshow in postscript. encoded << '?'; } break; } } textbuf << " (" << encoded.str() << ')'; } void Content::whitespace() { // we expect the charspace to be added around the space as well if(simple_font) { textbuf << " ( )"; if(cur_charspace) textbuf << ' ' << -1000 * (2 * cur_charspace); } else { // Tw doesn't work for multibyte fonts textbuf << " <0020>"; if(cur_wordspace) textbuf << ' ' << -1000 * (cur_wordspace + 2 * cur_charspace); } } void Content::whitespace(float width) { textbuf << ' ' << -1000 * width; } DocumentPtr Content::getDocument() { return document_; } void Content::commitText() { if(!textbuf.str().empty()) { data() << '[' << textbuf.str() << "] TJ\n"; textbuf.str(""); } } Page::Ptr Page::create(int width, int height, Ref::Ptr parent, Document::Ptr document) { return Ptr(new Page(width, height, parent, document)); } Page::Page(int width, int height, Ref::Ptr parent, Document::Ptr document) : document_(document), content(0), resources_(Resources::create()), num_reffed_fonts(0) { set_entry_name("Type", "Page"); set_entry("Parent", parent); set_entry("Resources", resources_); set_entry("MediaBox", rectangle(0, 0, width, height)); } Content::Ptr Page::get_content() { if(!content) { content = Content::create(Page::Ptr(this)); set_entry("Contents", document_->get_xrefs()->add_object(content)); } return content; } std::string Page::getFont(const font::FontInfo& font) { return resources_->registerFont(document_->getFontObject(font)); } std::string Page::registerXObj(Object::Ptr object) { return resources_->registerXObj(document_->get_xrefs()->add_object(object)); } void Page::set_parent(Ref::Ptr ref) { set_entry("Parent", ref); } Document::Document() : xrefs(XRefs::create()), page_tree_root(Dictionary::create()), page_tree_root_ref(xrefs->add_object(page_tree_root)), convert_failures(0) {} std::ostream& XRefs::write(std::ostream& out) { typedef std::vector PVec; PVec pos; for(Objects::iterator i = objects.begin(); i != objects.end(); i++) { pos.push_back(out.tellp()); out << *i; } objects.clear(); xref_offset = out.tellp(); out << "xref \n0 " << (pos.size()+1) << " \n" << std::setw(10) << std::setfill('0') << 0 << ' ' << std::setw(0) << std::setfill(' ') << "65535 f \n"; for(PVec::const_iterator i = pos.begin(); i != pos.end(); i++) out << std::setw(10) << std::setfill('0') << *i << ' ' << std::setw(0) << std::setfill(' ') << "00000 n \n"; return out; } Content::Ptr Document::add_page(int width, int height) { ///\todo There might be a page tree, its not always just a simple array. Page::Ptr page = Page::create(width, height, page_tree_root_ref, Document::Ptr(this)); pages.push_back(xrefs->add_object(page)); return page->get_content(); } std::ostream& Document::write(std::ostream& out) { out.setf(std::ios_base::boolalpha); { // 1.5 was the latest version as of sep 2004, but my acroread was older // than that :-) const int version_maj = 1, version_min = 4; out << "%PDF-" << version_maj << '.' << version_min << "\n" // Binary (>127) junk comment to make sure that e.g. ftp programs // treat the file as binary data: << "%\xff\xff\xff\xff\n"; } Dictionary::Ptr catalog = Dictionary::create(); Ref::Ptr rcatalog = xrefs->add_object(catalog); catalog->set_entry_name("Type", "Catalog"); catalog->set_entry("Pages", page_tree_root_ref); page_tree_root->set_entry_name("Type", "Pages"); page_tree_root->set_entry_int("Count", pages.size()); Array::Ptr kids = Array::create(); for(Pages::iterator i = pages.begin(); i != pages.end(); i++) kids->push_back(*i); page_tree_root->set_entry("Kids", kids); xrefs->write(out); Dictionary::Ptr trailer = Dictionary::create(); trailer->set_entry_int("Size", xrefs->get_num_of_refs()); trailer->set_entry("Root", rcatalog); out << "trailer\n" << trailer << "startxref \n" << xrefs->get_xref_offset() << " \n%%EOF\n"; return out; } }; // The PDF Reference counts bits as LSB=1. inline int PdfBit(int pos) { return 1 << (pos - 1); } PDF::Stream::Ptr makeFontStream(std::istream& in, const std::string fontfilename) { PDF::Stream::Ptr fontstream = PDF::Stream::create(); // Ignore anything before %! in the font file. char ch; while(in.get(ch) && !((ch == '%') && in.get(ch) && (ch == '!'))); if(!in) throw std::runtime_error("No PS startmark in " + fontfilename); fontstream->data() << "%!"; const std::ifstream::pos_type pos0 = in.tellg() - std::ifstream::off_type(2); debug << "pfa: pos0 = " << pos0 << "\n"; // Copy the entire "cleartext part" const std::string target = "currentfile eexec\n"; unsigned int found = 0; while(found < target.size() && in.get(ch)) { fontstream->data().put(ch); if(ch == target[found]) ++found; else found = 0; } if(!in) throw std::runtime_error("No eexec in " + fontfilename); const std::ifstream::pos_type pos1 = in.tellg(); debug << "pfa: pos1 = " << pos0 << "\n"; fontstream->set_entry_int("Length1", pos1 - pos0); // Copy the rest, which is the eexec part and the trailing zeroes. fontstream->data() << in.rdbuf(); const std::ifstream::pos_type pos3 = in.tellg(); // Here I assume that the trailing zeroes part is allways 513 chars long. // That is probably bad. fontstream->set_entry_int("Length2", pos3 - pos1 - 531); fontstream->set_entry_int("Length3", 531); return fontstream; } PDF::Dictionary::Ptr createCIDSystemInfoDict() { PDF::Dictionary::Ptr dict = PDF::Dictionary::create(); /// \todo I have no idea what these are supposed to be dict->set_entry("Registry", PDF::String::create("Adobe")); dict->set_entry("Ordering", PDF::String::create("Identity")); dict->set_entry("Supplement", PDF::Integer::create(0)); return dict; } PDF::Dictionary::Ptr createFontDescriptorDict(const std::string &fontname, const font::Metrics &metrics, PDF::XRefs::Ptr xrefs) { // And then the FontDescriptor PDF::Dictionary::Ptr descriptor = PDF::Dictionary::create(); descriptor->set_entry_name("Type", "FontDescriptor"); descriptor->set_entry_name("FontName", fontname); float italicangle = metrics.getItalicAngle(); /// \todo get these flags right descriptor->set_entry_int("Flags", PdfBit(1) * 0 /* Todo: fixed pitch */ + PdfBit(2) * 1 /* Todo: Serif */ + PdfBit(3) * 0 /* Todo: Symbolic */ + PdfBit(4) * 0 /* Todo: Script */ + PdfBit(6) * 1 /* !Symbolic */ + PdfBit(7) * (italicangle < -3) /* arbitrary */ + PdfBit(17) * 0 /* AllCap */ + PdfBit(18) * 0 /* SmallCap */ + PdfBit(19) * 0 /* don't force bold */); descriptor->set_entry("FontBBox", PDF::rectangle(metrics.getBBox(0), metrics.getBBox(1), metrics.getBBox(2), metrics.getBBox(3))); descriptor->set_entry("ItalicAngle", PDF::Real::create(italicangle)); descriptor->set_entry_int("Ascent", int(1000 * metrics.getAscender())); descriptor->set_entry_int("Descent", int(1000 * metrics.getDescender())); descriptor->set_entry_int("CapHeight", int(1000 * metrics.getCapHeight())); descriptor->set_entry_int("XHeight", int(1000 * metrics.getExHeight())); descriptor->set_entry_int("StemV", 0); /// \todo StemV // Include font file const std::string &fontfilename = font::FontManager::instance().getFontFile(fontname); const std::string suff = suffix(fontfilename); if(suff == "pfa") { std::ifstream pfa(fontfilename.c_str()); descriptor->set_entry("FontFile", makeFontStream(pfa, fontfilename)); } else if(suff == "pfb") { PDF::Stream::Ptr fontstream = PDF::Stream::create(); std::vector lengths; std::ifstream pfb(fontfilename.c_str()); PS::pfb2pfa(pfb, fontstream->data(), &lengths); switch(lengths.size()) { case 3: /// \todo set Length3 to 0 if not present? fontstream->set_entry_int("Length3", lengths[2]); // no break case 2: fontstream->set_entry_int("Length2", lengths[1]); fontstream->set_entry_int("Length1", lengths[0]); break; default: throw std::runtime_error("Bad number of blocks, " + tostr(lengths.size()) + ", in pfb " + fontfilename); } descriptor->set_entry("FontFile", xrefs->add_object(fontstream)); } else if(suff == "ttf") { // we only support truetype PDF::Stream::Ptr fontstream = PDF::Stream::create(); std::ifstream ttf(fontfilename.c_str()); fontstream->data() << ttf.rdbuf(); // Length1 is the length of the file after it has been decoded by // the the filter specified in Filter, but we are not using a // filter, so Length1 should be the same as Length fontstream->set_entry_int("Length1", fontstream->rawdata().length()); // Type 1 uses the "FontFile" entry, while TrueType uses the // "FontFile2" entry. descriptor->set_entry("FontFile2", xrefs->add_object(fontstream)); } else { throw std::runtime_error("Unknown suffix for font file: " + fontfilename); } return descriptor; } PDF::Dictionary::Ptr createCIDFontDict(const font::FontInfo& fontinfo, PDF::XRefs::Ptr xrefs) { PDF::Dictionary::Ptr dict = PDF::Dictionary::create(); dict->set_entry_name("Type", "Font"); // different subtypes depending on whether it2 contains Type1 or TrueType dict->set_entry_name("Subtype", getFontType(fontinfo) == TYPE1 ? "CIDFontType0" : "CIDFontType2"); dict->set_entry_name("BaseFont", fontinfo.getName()); dict->set_entry("CIDSystemInfo", createCIDSystemInfoDict()); PDF::Dictionary::Ptr descriptor = createFontDescriptorDict(fontinfo.getName(), fontinfo.getMetrics(), xrefs); dict->set_entry("FontDescriptor", xrefs->add_object(descriptor)); // assume TrueType const font::FTMetrics &metrics = dynamic_cast(fontinfo.getMetrics()); if(true) { // map CIDs to glyph indices in the TrueType font PDF::Stream::Ptr stream = PDF::Stream::create(); metrics.write_CIDToGIDMap(stream->data()); dict->set_entry("CIDToGIDMap", xrefs->add_object(stream)); } // character widths // The /W array is a list of pairs where the first element is a // start CID and the second is an array of widths for the start CID // and the following CIDs for which glyphs have been defined. PDF::Array::Ptr widths = PDF::Array::create(); gunichar c = 0; while(c < 0xffff) { // glyphs with index 0 are .notdef, so skip them if(!metrics.get_glyph_index(c)) { c++; continue; } // start CID widths->push_back(PDF::Integer::create(c)); // an array of widths for consecutive defined glyphs PDF::Array::Ptr iwidths = PDF::Array::create(); while(c < 0xffff && metrics.get_glyph_index(c)) { float w = metrics.getWidth(Glib::ustring(1, c)); iwidths->push_back(PDF::Integer::create(int(1000 * w))); c++; } widths->push_back(iwidths); } dict->set_entry("W", xrefs->add_object(widths)); return dict; } PDF::Stream::Ptr createCMapStream(const font::FontInfo& fontinfo) { PDF::Stream::Ptr stream = PDF::Stream::create(); stream->set_entry_name("Type", "CMap"); /// \todo The name of the cmap is foo?!? stream->set_entry_name("CMapName", "foo"); stream->set_entry("CIDSystemInfo", createCIDSystemInfoDict()); /// \todo we only support truetype so far // dynamic_cast(fontinfo.getMetrics()) // .get_mapping_table().write_CMap(stream->data()); return stream; } PDF::Dictionary::Ptr createFont0Dict(const font::FontInfo &fontinfo, PDF::Ref::Ptr cidfontdict, PDF::Ref::Ptr cmap) { const std::string& fontname = fontinfo.getName(); PDF::Dictionary::Ptr dict = PDF::Dictionary::create(); dict->set_entry_name("Type", "Font"); dict->set_entry_name("Subtype", "Type0"); dict->set_entry_name("BaseFont", fontname); // Use a cmap if we are given one if(cmap) dict->set_entry("Encoding", cmap); else dict->set_entry("Encoding", PDF::Name::create("Identity-H")); PDF::Array::Ptr dfonts = PDF::Array::create(); dfonts->push_back(cidfontdict); dict->set_entry("DescendantFonts", dfonts); return dict; } PDF::Dictionary::Ptr createFontDict(const font::FontInfo& fontinfo, PDF::XRefs::Ptr xrefs) { const std::string& fontname = fontinfo.getName(); // check if the fontname is in the list of standard fonts bool is_standard_font = false; for(const char **sf = &standard_fonts[0]; *sf; sf++) if(fontname == *sf) { is_standard_font = true; break; } FontType font_type = getFontType(fontinfo); // First the basics, needed for all fonts. PDF::Dictionary::Ptr dict = PDF::Dictionary::create(); dict->set_entry_name("Type", "Font"); dict->set_entry_name("Subtype", font_type == TYPE1 ? "Type1" : "TrueType"); dict->set_entry_name("BaseFont", fontname); dict->set_entry_name("Encoding", "MacRomanEncoding"); // If it's one of the 14 standard fonts, we don't have to do more. if(is_standard_font) return dict; // glyph metrics PDF::Array::Ptr widths = PDF::Array::create(); const font::Metrics& metrics(fontinfo.getMetrics()); // MacRoman seems to go by different names in different // installations ... /// \todo ugly hack and duplicated from Content::show bool macromanexists = true; try { Glib::IConv("MacRoman", "UTF-8"); } catch(...) { macromanexists = false; } Glib::IConv frommacroman("UTF-8", macromanexists ? "MacRoman" : "Mac"); for(unsigned char c = 32; c <= 254; ++c) { try { Glib::ustring converted = frommacroman.convert(std::string(1, c)); float w = metrics.getWidth(converted); widths->push_back(PDF::Integer::create(int(1000 * w))); } catch(const Glib::ConvertError &e) { warning << "Failed to convert char #" << int(c) << " from MacRoman" << std::endl; // PDF doesn't seem to have anything like glyphshow in postscript. widths->push_back(PDF::Integer::create(0)); } } dict->set_entry_int("FirstChar", 32); dict->set_entry_int("LastChar", 254); dict->set_entry("Widths", widths); PDF::Dictionary::Ptr descriptor(createFontDescriptorDict(fontname, metrics, xrefs)); dict->set_entry("FontDescriptor", descriptor); // Optional: ToUnicode return dict; } PDF::Document::FontWithName PDF::Document::getFontObject(const font::FontInfo& font) { Fonts::const_iterator i = used_font.find(font.getName()); if(i != used_font.end()) return i->second; Dictionary::Ptr fdict; // we only support CIDs for TrueType for now if(getFontType(font) == TRUETYPE) { Ref::Ptr cmap; //xrefs.add_object(createCMapStream(font)); Ref::Ptr cidfontdict(xrefs->add_object(createCIDFontDict(font, xrefs))); fdict = createFont0Dict(font, cidfontdict, cmap); } else { // Type1 as simple font fdict = createFontDict(font, xrefs); } FontWithName fwn(xrefs->add_object(fdict), "F" + tostr(used_font.size()+1)); used_font.insert(std::pair(font.getName(), fwn)); return used_font[font.getName()]; } #ifdef STAND_ALONE int main(int argc, char *argv[]) { PDF::Document document; document.add_page(200, 300); document.selectfont(20); document.moveto(20, 20); document.show("Hello, World!"); std::cerr << document.write(&std::cout) << " bytes written" << std::endl; } #endif