// -------------------------------------------------------------------- // PDF parsing // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipepdfparser.h" #include "ipeutils.h" //------------------------------------------------------------------------ // A '1' in this array means the character is white space. // A '1' or '2' means the character ends a name or command. // '2' == () {} [] <> / % static char specialChars[256] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx }; // -------------------------------------------------------------------- inline int ToInt(IpeString &s) { return std::strtol(s.CString(), 0, 10); } inline double ToDouble(IpeString &s) { return std::strtod(s.CString(), 0); } // -------------------------------------------------------------------- /*! \class IpePdfObj * \ingroup base * \brief Abstract base class for PDF objects. */ //! Pure virtual destructor. IpePdfObj::~IpePdfObj() { // nothing } //! Return this object as PDF null object. const IpePdfNull *IpePdfObj::Null() const { return 0; } //! Return this object as PDF bool object. const IpePdfBool *IpePdfObj::Bool() const { return 0; } //! Return this object as PDF number object. const IpePdfNumber *IpePdfObj::Number() const { return 0; } //! Return this object as PDF string object. const IpePdfString *IpePdfObj::String() const { return 0; } //! Return this object as PDF name object. const IpePdfName *IpePdfObj::Name() const { return 0; } //! Return this object as PDF reference object. const IpePdfRef *IpePdfObj::Ref() const { return 0; } //! Return this object as PDF array object. const IpePdfArray *IpePdfObj::Array() const { return 0; } //! Return this object as PDF dictionary object. const IpePdfDict *IpePdfObj::Dict() const { return 0; } //! Return PDF representation of the object. IpeString IpePdfObj::Repr() const { IpeString d; IpeStringStream ss(d); Write(ss); return d; } /*! \class IpePdfNull * \ingroup base * \brief The PDF null object. */ const IpePdfNull *IpePdfNull::Null() const { return this; } void IpePdfNull::Write(IpeStream &stream) const { stream << "null"; } /*! \class IpePdfBool * \ingroup base * \brief The PDF bool object. */ const IpePdfBool *IpePdfBool::Bool() const { return this; } void IpePdfBool::Write(IpeStream &stream) const { stream << (iValue ? "true" : "false"); } /*! \class IpePdfNumber * \ingroup base * \brief The PDF number object. */ const IpePdfNumber *IpePdfNumber::Number() const { return this; } void IpePdfNumber::Write(IpeStream &stream) const { stream << iValue; } /*! \class IpePdfString * \ingroup base * \brief The PDF string object. */ const IpePdfString *IpePdfString::String() const { return this; } void IpePdfString::Write(IpeStream &stream) const { stream << "(" << iValue << ")"; } /*! \class IpePdfName * \ingroup base * \brief The PDF name object. */ const IpePdfName *IpePdfName::Name() const { return this; } void IpePdfName::Write(IpeStream &stream) const { stream << "/" << iValue; } /*! \class IpePdfRef * \ingroup base * \brief The PDF reference object (indirect object). */ const IpePdfRef *IpePdfRef::Ref() const { return this; } void IpePdfRef::Write(IpeStream &stream) const { stream << iValue << " 0 R"; } /*! \class IpePdfArray * \ingroup base * \brief The PDF array object. */ const IpePdfArray *IpePdfArray::Array() const { return this; } void IpePdfArray::Write(IpeStream &stream) const { IpeString sep = "["; for (int i = 0; i < Count(); ++i) { stream << sep; sep = " "; Obj(i, 0)->Write(stream); } stream << "]"; } IpePdfArray::~IpePdfArray() { for (std::vector::iterator it = iObjects.begin(); it != iObjects.end(); ++it) { delete *it; *it = 0; } } //! Append an object to array. /*! Array takes ownership of the object. */ void IpePdfArray::Append(const IpePdfObj *obj) { iObjects.push_back(obj); } //! Return object with \a index in array. /*! Indirect objects (references) are looked up if \a file is not zero, and the object referred to is returned (0 if it does not exist). Object remains owned by array. */ const IpePdfObj *IpePdfArray::Obj(int index, const IpePdfFile *file) const { const IpePdfObj *obj = iObjects[index]; if (file && obj->Ref()) { int n = obj->Ref()->Value(); return file->Object(n); } return obj; } /*! \class IpePdfDict * \ingroup base * \brief The PDF dictionary and stream objects. A dictionary may or may not have attached stream data. */ const IpePdfDict *IpePdfDict::Dict() const { return this; } void IpePdfDict::Write(IpeStream &stream) const { IpeString sep = "<<"; for (std::vector::const_iterator it = iItems.begin(); it != iItems.end(); ++it) { stream << sep; sep = " "; stream << "/" << it->iKey << " "; it->iVal->Write(stream); } stream << ">>"; if (iStream.size() > 0) { stream << "\nstream\n"; for (int i = 0; i < iStream.size(); ++i) stream.PutChar(iStream[i]); stream << "\nendstream"; } } IpePdfDict::~IpePdfDict() { for (std::vector::iterator it = iItems.begin(); it != iItems.end(); ++it) { delete it->iVal; it->iVal = 0; } } //! Add stream data to this dictionary. void IpePdfDict::SetStream(const IpeBuffer &stream) { iStream = stream; } //! Add a (key, value) pair to the dictionary. /*! Dictionary takes ownership of \a obj. */ void IpePdfDict::Add(IpeString key, const IpePdfObj *obj) { Item item; item.iKey = key; item.iVal = obj; iItems.push_back(item); } //! Look up key in dictionary. /*! Indirect objects (references) are looked up if \a file is not zero, and the object referred to is returned. Returns 0 if key is not in dictionary. */ const IpePdfObj *IpePdfDict::Get(IpeString key, const IpePdfFile *file) const { for (std::vector::const_iterator it = iItems.begin(); it != iItems.end(); ++it) { if (it->iKey == key) { if (file && it->iVal->Ref()) return file->Object(it->iVal->Ref()->Value()); else return it->iVal; } } return 0; // not in dictionary } //! Is this stream compressed with flate compression? bool IpePdfDict::Deflated() const { const IpePdfObj *f = Get("Filter", 0); return !(!f || !f->Name() || f->Name()->Value() != "FlateDecode"); } #if 0 //! Return the (uncompressed) stream data. /*! This only handles the /Flate compression. */ IpeBuffer IpePdfDict::Inflate() const { if (iStream.size() == 0) return iStream; const IpePdfObj *f = Get("Filter", 0); if (!f || !f->Name() || f->Name()->Value() != "FlateDecode") return iStream; IpeString dest; IpeBufferSource bsource(iStream); IpeInflateSource source(bsource); int ch = source.GetChar(); while (ch != EOF) { dest += char(ch); ch = source.GetChar(); } return IpeBuffer(dest.data(), dest.size()); } #endif // -------------------------------------------------------------------- /*! \class IpePdfParser * \ingroup base * \brief PDF parser The parser understands the syntax of PDF files, but very little of its semantics. It is meant to be able to parse PDF documents created by Ipe for loading, and to extract information from PDF files created by Pdflatex. The parser reads a PDF file sequentially from front to back, ignores the contents of 'xref' sections, stores only generation 0 objects, and stops after reading the first 'trailer' section (so it cannot deal with files with incremental updates). It cannot handle stream objects whose /Length entry has been deferred (using an indirect object). */ //! Construct with a data source. IpePdfParser::IpePdfParser(IpeDataSource &source) : iSource(source) { iPos = 0; GetChar(); // init iCh GetToken(); // init iTok } //! Skip white space and comments. void IpePdfParser::SkipWhiteSpace() { while (!Eos() && (specialChars[iCh] == 1 || iCh == '%')) { // handle comment if (iCh == '%') { while (!Eos() && iCh != '\n' && iCh != '\r') GetChar(); } GetChar(); } } //! Read the next token from the input stream. void IpePdfParser::GetToken() { iTok.iString.erase(); iTok.iType = IpePdfToken::EErr; SkipWhiteSpace(); if (Eos()) return; // Err // parse string if (iCh == '(') { int nest = 0; GetChar(); while (iCh != ')' || nest > 0) { if (Eos()) return; // Err if (iCh == '\\') { GetChar(); if ('0' <= iCh && iCh <= '9') { // octal char code char buf[4]; int i = 0; buf[i++] = char(iCh); GetChar(); if ('0' <= iCh && iCh <= '9') { buf[i++] = char(iCh); GetChar(); } if ('0' <= iCh && iCh <= '9') { buf[i++] = char(iCh); GetChar(); } buf[i] = '\0'; iTok.iString.append(char(std::strtol(buf, 0, 8))); } else { iTok.iString.append(char(iCh)); GetChar(); } } else { if (iCh == '(') ++nest; else if (iCh == ')') --nest; iTok.iString.append(char(iCh)); GetChar(); } } GetChar(); // skip closing ')' iTok.iType = IpePdfToken::EString; return; } if (iCh == '<') { GetChar(); // recognize dictionary separator "<<" if (iCh == '<') { GetChar(); iTok.iType = IpePdfToken::EDictBg; return; } // otherwise it's a binary string while (iCh != '>') { if (Eos()) return; // Err iTok.iString.append(char(iCh)); GetChar(); } // We don't bother to decode it GetChar(); // skip '>' iTok.iType = IpePdfToken::EString; ipeDebug("Found binary string <%s>", iTok.iString.CString()); return; } int ch = iCh; iTok.iString.append(char(iCh)); GetChar(); // recognize array separators if (ch == '[') { iTok.iType = IpePdfToken::EArrayBg; return; } else if (ch == ']') { iTok.iType = IpePdfToken::EArrayEnd; return; } // recognize dictionary separator ">>" if (ch == '>') { if (iCh != '>') return; // Err GetChar(); iTok.iType = IpePdfToken::EDictEnd; return; } // collect all characters up to white-space or separator while (!specialChars[iCh]) { if (Eos()) return; // Err iTok.iString.append(char(iCh)); GetChar(); } if ('0' <= ch && ch <= '9' || ch == '+' || ch == '-' || ch == '.') iTok.iType = IpePdfToken::ENumber; else if (ch == '/') iTok.iType = IpePdfToken::EName; else if (iTok.iString == "null") iTok.iType = IpePdfToken::ENull; else if (iTok.iString == "true") iTok.iType = IpePdfToken::ETrue; else if (iTok.iString == "false") iTok.iType = IpePdfToken::EFalse; else iTok.iType = IpePdfToken::EOp; } // -------------------------------------------------------------------- //! Parse elements of an array. IpePdfArray *IpePdfParser::MakeArray() { IpeAutoPtr arr(new IpePdfArray); for (;;) { if (iTok.iType == IpePdfToken::EArrayEnd) { // finish array GetToken(); return arr.Take(); } // check for reference object if (iTok.iType == IpePdfToken::ENumber) { IpePdfToken t1 = iTok; GetToken(); if (iTok.iType == IpePdfToken::ENumber) { IpePdfToken t2 = iTok; GetToken(); if (iTok.iType == IpePdfToken::EOp && iTok.iString == "R") { arr->Append(new IpePdfRef(ToInt(t1.iString))); GetToken(); } else { arr->Append(new IpePdfNumber(ToDouble(t1.iString))); arr->Append(new IpePdfNumber(ToDouble(t2.iString))); } } else { arr->Append(new IpePdfNumber(ToDouble(t1.iString))); } } else { IpePdfObj *obj = GetObject(); if (!obj) return 0; arr->Append(obj); } } } IpePdfDict *IpePdfParser::MakeDict() { IpeAutoPtr dict(new IpePdfDict); for (;;) { if (iTok.iType == IpePdfToken::EDictEnd) { // finish GetToken(); // check whether stream follows if (iTok.iType != IpePdfToken::EOp || iTok.iString != "stream") return dict.Take(); // time to read the stream while (!Eos() && iCh != '\n') GetChar(); GetChar(); // skip '\n' // now at beginning of stream const IpePdfObj *len = dict->Get("Length", 0); if (!len || !len->Number()) return 0; int bytes = int(len->Number()->Value()); IpeBuffer buf(bytes); char *p = buf.data(); while (bytes--) { *p++ = char(iCh); GetChar(); } dict->SetStream(buf); GetToken(); if (iTok.iType != IpePdfToken::EOp || iTok.iString != "endstream") return 0; GetToken(); return dict.Take(); } // must read name if (iTok.iType != IpePdfToken::EName) return 0; IpeString name = iTok.iString.substr(1); GetToken(); // check for reference object if (iTok.iType == IpePdfToken::ENumber) { IpePdfToken t1 = iTok; GetToken(); if (iTok.iType == IpePdfToken::ENumber) { IpePdfToken t2 = iTok; GetToken(); if (iTok.iType == IpePdfToken::EOp && iTok.iString == "R") { dict->Add(name, new IpePdfRef(ToInt(t1.iString))); GetToken(); } else return 0; // should be name or '>>' } else dict->Add(name, new IpePdfNumber(ToDouble(t1.iString))); } else { IpePdfObj *obj = GetObject(); if (!obj) return 0; dict->Add(name, obj); } } } //! Read one object from input stream. IpePdfObj *IpePdfParser::GetObject() { IpePdfToken tok = iTok; GetToken(); switch (tok.iType) { case IpePdfToken::ENumber: return new IpePdfNumber(std::strtod(tok.iString.CString(), 0)); case IpePdfToken::EString: return new IpePdfString(tok.iString); case IpePdfToken::EName: return new IpePdfName(tok.iString.substr(1)); case IpePdfToken::ENull: return new IpePdfNull; case IpePdfToken::ETrue: return new IpePdfBool(true); case IpePdfToken::EFalse: return new IpePdfBool(false); case IpePdfToken::EArrayBg: return MakeArray(); case IpePdfToken::EDictBg: return MakeDict(); // anything else is an error case IpePdfToken::EErr: default: return 0; } } //! Parse an object definition (current token is object number). IpePdfObj *IpePdfParser::GetObjectDef() { GetToken(); if (iTok.iType != IpePdfToken::ENumber || iTok.iString != "0") return 0; GetToken(); if (iTok.iType != IpePdfToken::EOp || iTok.iString != "obj") return 0; GetToken(); IpePdfObj *obj = GetObject(); if (!obj) return 0; if (iTok.iType != IpePdfToken::EOp || iTok.iString != "endobj") return 0; GetToken(); return obj; } //! Skip xref table (current token is 'xref') void IpePdfParser::SkipXRef() { GetToken(); // first object number GetToken(); // number of objects int k = ToInt(iTok.iString); GetToken(); while (k--) { GetToken(); // obj num GetToken(); // gen num GetToken(); // n or f } } //! Parse trailer dictionary (current token is 'trailer') IpePdfDict *IpePdfParser::GetTrailer() { GetToken(); if (iTok.iType != IpePdfToken::EDictBg) return 0; GetToken(); return MakeDict(); } // -------------------------------------------------------------------- /*! \class IpePdfFile * \ingroup base * \brief All information obtained by parsing a PDF file. */ //! Create empty container. IpePdfFile::IpePdfFile() { iTrailer = 0; } // Destroy all the objects from the file. IpePdfFile::~IpePdfFile() { delete iTrailer; std::map::const_iterator it; for (it = iObjects.begin(); it != iObjects.end(); ++it) { delete it->second; } } //! Parse entire PDF stream, and store objects. bool IpePdfFile::Parse(IpeDataSource &source) { IpePdfParser parser(source); for (;;) { IpePdfToken t = parser.Token(); if (t.iType == IpePdfToken::ENumber) { // 0 obj starts an object int num = ToInt(t.iString); IpePdfObj *obj = parser.GetObjectDef(); if (!obj) return false; iObjects[num] = obj; } else if (t.iType == IpePdfToken::EOp) { if (t.iString == "trailer") { iTrailer = parser.GetTrailer(); if (!iTrailer) return false; return true; } else if (t.iString == "xref") { parser.SkipXRef(); } else // don't know what's happening return false; } else // don't know what's happening return false; } } //! Return object with number \a num. const IpePdfObj *IpePdfFile::Object(int num) const { std::map::const_iterator it = iObjects.find(num); if (it != iObjects.end()) return it->second; else return 0; } //! Return root catalog of PDF file. const IpePdfDict *IpePdfFile::Catalog() const { const IpePdfObj *root = iTrailer->Get("Root", this); assert(root && root->Dict()); return root->Dict(); } //! Return first page of the document. const IpePdfDict *IpePdfFile::Page() const { const IpePdfObj *pages = Catalog()->Get("Pages", this); assert(pages && pages->Dict()); const IpePdfObj *kids = pages->Dict()->Get("Kids", this); assert(kids); if (!kids->Array()) return 0; const IpePdfObj *page = kids->Array()->Obj(0, this); // this should be page 1 if (!page || !page->Dict()) return 0; return page->Dict(); } // --------------------------------------------------------------------