/* * PDFedit - free program for PDF document manipulation. * Copyright (C) 2006, 2007 PDFedit team: Michal Hocko, * Miroslav Jahoda, * Jozef Misutka, * Martin Petricek * * Project is hosted on http://sourceforge.net/projects/pdfedit */ // vim:tabstop=4:shiftwidth=4:noexpandtab:textwidth=80 /* * ===================================================================================== * Filename: cpage.cc * Created: 03/20/2006 11:41:43 AM CET * Author: jmisutka (), * ===================================================================================== */ // static #include "static.h" // Page #include "cpage.h" // CContenteStream #include "ccontentstream.h" //Cpdf #include "cpdf.h" // Helper functions #include "cobject.h" #include "factories.h" #include "observer.h" // ===================================================================================== namespace pdfobjects { // ===================================================================================== using namespace std; using namespace boost; using namespace utils; using namespace observer; // ===================================================================================== namespace { // ===================================================================================== /** * Page attributes structure of dictionary properties which can be inherited from a parent * in the page tree. * * If an inheritable property is not present in a page it is defined in one of * its parents in the page tree. */ struct InheritedPageAttr { boost::shared_ptr resources; boost::shared_ptr mediaBox; boost::shared_ptr cropBox; boost::shared_ptr rotate; }; /** * Fills InheritedPageAttr structure for a given page dictionary. * * Recursive function which checks given pageDict whether it contains * uninitialized (NULL values) from a given attribute structure. If true, sets * the value from the dictionary. If at least one property is still not * initialized, repeats the process for the parent dictionary (dereferenced * "Parent" property). End if the "Parent" property is not present (in root * of a page tree). *
* Use default value when a property was not initialized. *
* Note that attrs structure comes out allways initialized when recursion is * finished. * * @param pageDict Page dictionary. * @param attrs Output attribute structure where correct values are put. * * @throw NotImplementedException at this moment. */ void fillInheritedPageAttr(const boost::shared_ptr pageDict, InheritedPageAttr & attrs) { int initialized=0; // TODO consolidate code - get rid of copy & paste // resource field if(!attrs.resources.get()) { // attrs.resources field is not specified yet, so tries this dictionary if(pageDict->containsProperty("Resources")) { shared_ptr prop=pageDict->getProperty("Resources"); if(isRef(prop)) { attrs.resources=getCObjectFromRef(prop); ++initialized; } else if(isDict(prop)) { attrs.resources=IProperty::getSmartCObjectPtr(prop); ++initialized; } } }else ++initialized; // mediabox field if(!attrs.mediaBox.get()) { // attrs.mediaBox field is not specified yet, so tries this array if(pageDict->containsProperty("MediaBox")) { shared_ptr prop=pageDict->getProperty("MediaBox"); if(isRef(prop)) { attrs.mediaBox=getCObjectFromRef(prop); ++initialized; }else if(isArray(prop)) { attrs.mediaBox=IProperty::getSmartCObjectPtr(prop); ++initialized; } } }else ++initialized; // cropbox field if(!attrs.cropBox.get()) { // attrs.cropBox field is not specified yet, so tries this array if(pageDict->containsProperty("CropBox")) { shared_ptr prop=pageDict->getProperty("CropBox"); if(isRef(prop)) { attrs.cropBox=getCObjectFromRef(prop); ++initialized; }else if(isArray(prop)) { attrs.cropBox=IProperty::getSmartCObjectPtr(prop); ++initialized; } } }else ++initialized; // rotate field if(!attrs.rotate.get()) { // attrs.rotate field is not specified yet, so tries this array if(pageDict->containsProperty("Rotate")) { shared_ptr prop=pageDict->getProperty("Rotate"); if(isRef(prop)) { attrs.rotate=getCObjectFromRef(prop); ++initialized; }else if(isInt(prop)) { attrs.rotate=IProperty::getSmartCObjectPtr(prop); ++initialized; } } }else ++initialized; // all values available from this dictionary are set now if(initialized<4) { // not everything from InheritedPageAttr is initialized now // tries to initialize from parent. // If parent is not present (root of page tree hierarchy is reached), // stops recursion and initializes values with default if(pageDict->containsProperty("Parent")) { shared_ptr parentRef=pageDict->getProperty("Parent"); if(!isRef(parentRef)) // this should not happen - malformed page tree structure return; shared_ptr parentDict=getCObjectFromRef(parentRef); fillInheritedPageAttr(parentDict, attrs); }else { // Resources is required and at least empty dictionary should be // specified if(!attrs.resources.get()) attrs.resources=shared_ptr(CDictFactory::getInstance()); // default A4 sized box Rectangle defaultRect( DisplayParams::DEFAULT_PAGE_LX, DisplayParams::DEFAULT_PAGE_LY, DisplayParams::DEFAULT_PAGE_RX, DisplayParams::DEFAULT_PAGE_RY ); // MediaBox is required and specification doesn't say anything about // default value - we are using standard A4 format if(!attrs.mediaBox.get()) attrs.mediaBox=IProperty::getSmartCObjectPtr(getIPropertyFromRectangle(defaultRect)); // CropBox is optional and specification doesn't say anything about // default value - we are using standard A4 format if(!attrs.cropBox.get()) attrs.cropBox=IProperty::getSmartCObjectPtr(getIPropertyFromRectangle(defaultRect)); // Rotate is optional and specification defines default value to 0 if(!attrs.rotate.get()) { // gcc workaround // direct usage of static DEFAULT_ROTATE value caused linkage // error int defRot=DisplayParams::DEFAULT_ROTATE; attrs.rotate=shared_ptr(CIntFactory::getInstance(defRot)); } } } } // ===================================================================================== } // namespace // ===================================================================================== // // // void setInheritablePageAttr(boost::shared_ptr & pageDict) { InheritedPageAttr attrs; fillInheritedPageAttr(pageDict, attrs); // checks Resources if(!pageDict->containsProperty("Resources")) pageDict->addProperty("Resources", *(attrs.resources)); // checks MediaBox if(!pageDict->containsProperty("MediaBox")) pageDict->addProperty("MediaBox", *(attrs.mediaBox)); // checks CropBox if(!pageDict->containsProperty("CropBox")) pageDict->addProperty("CropBox", *(attrs.cropBox)); // checks Rotate if(!pageDict->containsProperty("Rotate")) pageDict->addProperty("Rotate", *(attrs.rotate)); } // ===================================================================================== namespace { // ===================================================================================== /** Helper method to extract annotations array from page dictionary. * @param pageDict Page dictionary. * * If Annots property is reference, dereferece it and checks whether target * property is array. * * @throw ElementNotFoundException if given page dictionary doesn't contain * Annots property. * @throw ElementBadTypeException if Annots property exists but it is not an * array or reference to array. * * @throw ElementNotFoundException if Annots array is not present in given * dictionary. * @return Annotation array. */ shared_ptr getAnnotsArray(shared_ptr pageDict) { shared_ptr annotsArray; shared_ptr arrayProp=pageDict->getProperty("Annots"); if(isRef(arrayProp)) // this will throw if target is not an array annotsArray=getCObjectFromRef(arrayProp); else annotsArray=IProperty::getSmartCObjectPtr(arrayProp); // just to be sure that return value is initialized assert(annotsArray.get()); return annotsArray; } /** Collects all annotations from given page dictionary. * @param pageDict Page dictionary. * @param container Container where to place annotations. * * Checks Annots property from given dictionary and if it is an array (or * reference to array) then continue, otherwise immediatelly returns. * All members which are referencies and points to dictionaries are used to * create new CAnnotation instance which is placed to given container. *
* Note that given container is cleared in any case. */ template void collectAnnotations(boost::shared_ptr pageDict, Container & container) { // clears given container container.clear(); try { // gets annotation array from page dictionary shared_ptr annotsArray=getAnnotsArray(pageDict); // gets all Annots elements - these has to be referencies to // dictionaries for(size_t i=0; igetPropertyCount(); ++i) { // gets elements and ignores those which are not referencies shared_ptr elem=annotsArray->getProperty(i); if(!isRef(elem)) { kernelPrintDbg(debug::DBG_WARN, "Annots["< annotDict=getCObjectFromRef(elem); // creates CAnnotation instance and inserts it to the container shared_ptr annot(new CAnnotation(annotDict)); container.push_back(annot); }catch(ElementBadTypeException & e) { kernelPrintDbg(debug::DBG_WARN, "Annots["< annotsArray; if(isRef(annots)) { annots->unregisterObserver(annotsPropWatchDog); try { annotsArray=getCObjectFromRef(annots); }catch(CObjectException & e) { IndiRef ref=getValueFromSimple(annots); kernelPrintDbg(DBG_WARN, ref<<" doesn't point to array."); } }else annotsArray=IProperty::getSmartCObjectPtr(annots); // if no array is present, skips further steps if(!annotsArray.get()) { kernelPrintDbg(DBG_WARN, "Given annots is not or doesn't refer to array."); return; } ChildrenStorage children; annotsArray->unregisterObserver(annotsArrayWatchDog); annotsArray->_getAllChildObjects(children); for(ChildrenStorage::iterator i=children.begin(); i!=children.end(); ++i) { shared_ptr child=*i; if(isRef(child)) child->unregisterObserver(annotsArrayWatchDog); } } void CPage::registerAnnotsObservers(shared_ptr & annots) { using namespace boost; using namespace debug; kernelPrintDbg(DBG_DBG, ""); shared_ptr annotsArray; if(isRef(annots)) { annots->registerObserver(annotsPropWatchDog); try { annotsArray=getCObjectFromRef(annots); }catch(CObjectException & e) { IndiRef ref=getValueFromSimple(annots); kernelPrintDbg(DBG_WARN, ref<<" doesn't point to array."); } }else annotsArray=IProperty::getSmartCObjectPtr(annots); // if no array is present, skips further steps if(!annotsArray.get()) { kernelPrintDbg(DBG_WARN, "Given annots is not or doesn't refer to array."); return; } ChildrenStorage children; annotsArray->registerObserver(annotsArrayWatchDog); annotsArray->_getAllChildObjects(children); for(ChildrenStorage::iterator i=children.begin(); i!=children.end(); ++i) { shared_ptr child=*i; if(isRef(child)) child->registerObserver(annotsArrayWatchDog); } } void CPage::consolidateAnnotsStorage(boost::shared_ptr & oldValue, boost::shared_ptr & newValue) { using namespace boost; using namespace debug; // handle original value - one which is removed or replaced // this has to be invalidated and removed from annotStorage // this is skipped if oldValue is not reference if(isRef(oldValue)) { try { shared_ptr annotDict=getCObjectFromRef(oldValue); CPage::AnnotStorage::iterator i; for(i=annotStorage.begin(); i!=annotStorage.end(); ++i) { shared_ptr annot=*i; if(annot->getDictionary()==annotDict) { kernelPrintDbg(debug::DBG_DBG, "Annotation maintaining oldValue found and removed. Invalidating annotation"); annot->invalidate(); annotStorage.erase(i); break; } } if(i==annotStorage.end()) kernelPrintDbg(debug::DBG_WARN, "Removed value is not in annotStorage.") }catch(ElementBadTypeException & e) { kernelPrintDbg(debug::DBG_WARN, "oldValue dereferenced value is not dictionary."); } } kernelPrintDbg(debug::DBG_DBG, "oldValue is handled now."); // handle new value - one which is added or replaces an old value // this has to be added to annotStorage // checks whether dereferenced property is dictionary. If not it // means that some mass is provided and so it is ignored // this is skipped if newValue is not reference if(isRef(newValue)) { try { shared_ptr annotDict=getCObjectFromRef(newValue); // creates CAnnotation instance from dereferenced dictionary // and adds it to annotStorage shared_ptr annot(new CAnnotation(annotDict)); annotStorage.push_back(annot); }catch(ElementBadTypeException & e) { kernelPrintDbg(debug::DBG_WARN, "Dereferenced newValue is not dictionary."); } } } // // // void CPage::AnnotsPropWatchDog::notify( boost::shared_ptr newValue, boost::shared_ptr context) const throw() { using namespace debug; using namespace observer; kernelPrintDbg(debug::DBG_DBG, ""); if(!context.get()) { kernelPrintDbg(DBG_DBG, "No context information."); return; } kernelPrintDbg(DBG_DBG, "context type="<getType()); shared_ptr oldValue; switch(context->getType()) { case BasicChangeContextType: { // This means that Annots property is reference and it has // changed its reference value shared_ptr > basicContext= dynamic_pointer_cast, const observer::IChangeContext >(context); oldValue=basicContext->getOriginalValue(); assert(isRef(newValue)); assert(isRef(oldValue)); } break; case ComplexChangeContextType: { // page dictionary has changed // checks identificator of changed property and if it is not // Annots, immediately returns shared_ptr basicContext= dynamic_pointer_cast >(context); if(!basicContext) { kernelPrintDbg(DBG_WARN, "Bad property identificator type."); return; } if(basicContext->getValueId()!="Annots") return; oldValue=basicContext->getOriginalValue(); // reference oldValue needs unregistration of this observer if(isRef(oldValue)) oldValue->unregisterObserver(page->annotsPropWatchDog); // if new value is reference, register this observer to it if(isRef(newValue)) newValue->registerObserver(page->annotsPropWatchDog); } break; default: kernelPrintDbg(DBG_WARN, "Unsupported context type"); } // gets original annots array and unregisters all observers // doesn't unregister observer from Annots property, because it is done only // in case of complex context shared_ptr oldArray; if(isRef(oldValue)) { try { oldArray=getCObjectFromRef(oldValue); }catch(CObjectException & e) { IndiRef ref=getValueFromSimple(oldValue); kernelPrintDbg(DBG_WARN, "Target of Annots "<(oldValue); if(oldArray.get()) page->unregisterAnnotsObservers(oldArray); // clears and invalidates all annotations from annotStorage kernelPrintDbg(DBG_DBG, "Discarding annotStorage."); for(AnnotStorage::iterator i=page->annotStorage.begin(); i!=page->annotStorage.end(); ++i) (*i)->invalidate(); page->annotStorage.clear(); // creates new annotStorage with new annotations kernelPrintDbg(DBG_DBG, "Creating new annotStorage."); collectAnnotations(page->dictionary, page->annotStorage); // registers obsevers to newValue annotation array - Annots property doesn't // need obsever registration for same reason as oldValue doesn't need // unregistration shared_ptr newArray; if(isRef(newValue)) { try { newArray=getCObjectFromRef(newValue); }catch(CObjectException & e) { IndiRef ref=getValueFromSimple(newValue); kernelPrintDbg(DBG_WARN, "Target of Annots "<(oldValue); if(newArray.get()) page->registerAnnotsObservers(newArray); kernelPrintDbg(debug::DBG_INFO, "Annotation consolidation done."); } // // // void CPage::AnnotsArrayWatchDog::notify( boost::shared_ptr newValue, boost::shared_ptr context) const throw() { using namespace debug; using namespace observer; kernelPrintDbg(debug::DBG_DBG, ""); if(!context.get()) { kernelPrintDbg(DBG_DBG, "No context information."); return; } kernelPrintDbg(DBG_DBG, "context type="<getType()); shared_ptr oldValue; switch(context->getType()) { case BasicChangeContextType: { // this means that Annots element reference value has changed shared_ptr > basicContext= dynamic_pointer_cast, const observer::IChangeContext >(context); oldValue=basicContext->getOriginalValue(); assert(isRef(newValue)); assert(isRef(oldValue)); // nothing with observers has to be done here } break; case ComplexChangeContextType: { // Annots array content has changed shared_ptr basicContext= dynamic_pointer_cast >(context); if(!context) { kernelPrintDbg(DBG_WARN, "Bad property identificator type."); return; } oldValue=basicContext->getOriginalValue(); // if oldValue is reference, unregisters this observer from it // because it is no more available if(isRef(oldValue)) oldValue->unregisterObserver(page->annotsArrayWatchDog); // if new value is reference registers this observer to it if(isRef(newValue)) newValue->registerObserver(page->annotsArrayWatchDog); } break; default: kernelPrintDbg(DBG_WARN, "Unsupported context type"); } page->consolidateAnnotsStorage(oldValue, newValue); kernelPrintDbg(debug::DBG_INFO, "Annotation consolidation done."); } // // // void CPage::ContentsWatchDog::notify (boost::shared_ptr newValue, boost::shared_ptr context) const throw() { kernelPrintDbg(debug::DBG_DBG, ""); assert (context); kernelPrintDbg (debug::DBG_DBG, "context type=" << context->getType()); switch(context->getType()) { case ComplexChangeContextType: { // // Is it a dictionary (probably Page dictionary) // shared_ptr ctxtdict = dynamic_pointer_cast > (context); if (ctxtdict) { shared_ptr oldValue = ctxtdict->getOriginalValue (); // Contents entry was removed if (isNull(newValue)) { // Unregister observer ctxtdict->getOriginalValue ()->unregisterObserver (page->contentsWatchDog); }else { // New contents entry if ("Contents" == ctxtdict->getValueId ()) page->registerContentsObserver (); } break; } // // Is it an array (Probably Contents) -- do nothing just reparse // shared_ptr ctxtarray = dynamic_pointer_cast > (context); if (ctxtarray) break; } break; default: assert (!"Unsupported context type"); break; } // Parse content streams (add or delete of object) page->parseContentStream (); // Indicate change page->_objectChanged (); } // // Constructor // CPage::CPage (boost::shared_ptr& pageDict) : dictionary(pageDict), valid (true), annotsPropWatchDog(new AnnotsPropWatchDog(this)), annotsArrayWatchDog(new AnnotsArrayWatchDog(this)), contentsWatchDog (new ContentsWatchDog(this)) { kernelPrintDbg (debug::DBG_DBG, ""); assert (pageDict); assert (valid); // Better not throw in a constructor // if (!isPage (pageDict)) // throw CObjInvalidObject (); // Fill inheritable properties but do not dispatch the change // if no change on this document occurs, we do not want to change it (but we // do it in the setInheritablePageAttr function) dictionary->lockChange(); setInheritablePageAttr (dictionary); // Workaround: pages that miss contents entry // -- it is not a global solution one can delete and add // contents entry of a page if (!dictionary->containsProperty("Contents")) { CArray array; dictionary->addProperty ("Contents",array); } dictionary->unlockChange(); // collects all annotations from this page and registers observers // annotsPropWatchDog has to be registered to page dictionary and the // rest is done by registerAnnotsObservers collectAnnotations(dictionary, annotStorage); dictionary->registerObserver(annotsPropWatchDog); if(dictionary->containsProperty("Annots")) { shared_ptr annotsProp=dictionary->getProperty("Annots"); registerAnnotsObservers(annotsProp); } // Create contents observer registerContentsObserver (); } // // Get methods // // // // Rectangle CPage::getMediabox () const { kernelPrintDbg (debug::DBG_DBG, ""); assert (valid); InheritedPageAttr atr; fillInheritedPageAttr (dictionary,atr); Rectangle rc; rc.xleft = getDoubleFromArray (atr.mediaBox, 0); rc.yleft = getDoubleFromArray (atr.mediaBox, 1); rc.xright = getDoubleFromArray (atr.mediaBox, 2); rc.yright = getDoubleFromArray (atr.mediaBox, 3); return rc; } // // // int CPage::getRotation () const { assert (valid); InheritedPageAttr atr; fillInheritedPageAttr (dictionary,atr); return atr.rotate->getValue(); } // // // void CPage::setRotation (int rot) { assert (valid); CInt crot (rot); dictionary->setProperty ("Rotate", crot); } // // // void CPage::addAnnotation(boost::shared_ptr annot) { kernelPrintDbg(debug::DBG_DBG, ""); assert (valid); // checks whether this page is valid (has pdf and valid reference. if(!hasValidPdf(dictionary) || !hasValidPdf(dictionary)) { kernelPrintDbg(debug::DBG_ERR, "This page doesn't belong to pdf or doesn't " << "have correct indiRef. It is not suitable for annotation."); throw CObjInvalidObject(); } // gets pdf of this annotation - it is valid CPdf * pdf=dictionary->getPdf(); // gets Annots array from page dictionary. If no found, creates it. If bad // typed, throws an exception shared_ptr annotsArray; try { annotsArray=getAnnotsArray(dictionary); }catch(ElementBadTypeException & e) { // TODO provide also bad type information kernelPrintDbg(debug::DBG_ERR, "Page's Annots field is malformed. Array property expected."); throw; } catch(ElementNotFoundException & e) { kernelPrintDbg(debug::DBG_INFO, "Page's Annots field missing. Creating one."); // Annots array doesn't exist - so creates one and registers observer to // it. // annotsArray must be set from getProperty method because addProperty // creates deep copy of given scoped_ptr tmpArray(CArrayFactory::getInstance()); dictionary->addProperty("Annots", *tmpArray); annotsArray=IProperty::getSmartCObjectPtr( dictionary->getProperty("Annots") ); annotsArray->registerObserver(annotsArrayWatchDog); } kernelPrintDbg(debug::DBG_DBG, "Creating new indirect dictionary for annotation."); // addes annotation dictionary to the pdf - this will add deep copy if given // dictionary and also solves problems with annotation from different pdf IndiRef annotRef=pdf->addIndirectProperty(annot->getDictionary()); // gets added annotation dictionary shared_ptr annotDict=IProperty::getSmartCObjectPtr( pdf->getIndirectProperty(annotRef) ); kernelPrintDbg(debug::DBG_DBG, "Setting annotation dictionary field P="<getIndiRef()); // updates P field with reference to this page // This is not explictly required by specification for all annotation types, // but is not an error to supply this information shared_ptr pageRef(CRefFactory::getInstance(dictionary->getIndiRef())); checkAndReplace(annotDict, "P", *pageRef); kernelPrintDbg(debug::DBG_INFO, "Adding reference "< annotCRef(CRefFactory::getInstance(annotRef)); annotsArray->addProperty(*annotCRef); } bool CPage::delAnnotation(boost::shared_ptr annot) { kernelPrintDbg(debug::DBG_DBG, ""); assert (valid); // searches annotation in annotStorage - which is synchronized with current // state of Annots array size_t pos=0; for(AnnotStorage::iterator i=annotStorage.begin(); i!=annotStorage.end(); ++i,++pos) { shared_ptr element=*i; if(annot!=element) continue; // annotation found, removes dictionary reference from Annots array IndiRef annotRef=element->getDictionary()->getIndiRef(); kernelPrintDbg(debug::DBG_DBG, "Annotation found. Indiref="< annotArray=getAnnotsArray(dictionary); // deleting of this reference triggers annotsWatchDog observer which // will synchronize annotStorage with current state annotArray->delProperty(pos); kernelPrintDbg(debug::DBG_INFO, "Annotation referece "<invalidate(); return true; }catch(CObjectException & e) { kernelPrintDbg(debug::DBG_CRIT, "Unexpected Annots array missing."); return false; } } kernelPrintDbg(debug::DBG_ERR, "Given annotation couldn't have been found."); return false; } // // // void CPage::getText (std::string& text, const string* encoding, const Rectangle* rc) const { kernelPrintDbg (debug::DBG_DBG, ""); assert (valid); // Create text output device boost::scoped_ptr textDev (new ::TextOutputDev (NULL, gFalse, gFalse, gFalse)); assert (textDev->isOk()); if (!textDev->isOk()) throw CObjInvalidOperation (); // Display page displayPage (*textDev); // Init xpdf mess xpdf::openXpdfMess (); // Set encoding if (encoding) globalParams->setTextEncoding(const_cast(encoding->c_str())); // Get the text if (NULL == rc) rc = &(lastParams.pageRect); boost::scoped_ptr gtxt (textDev->getText(rc->xleft, rc->yleft, rc->xright, rc->yright)); text = gtxt->getCString(); // Uninit xpdf mess xpdf::closeXpdfMess (); } // // Set methods // // // // void CPage::setMediabox (const Rectangle& rc) { kernelPrintDbg (debug::DBG_DBG, " [" << rc << "]"); assert (valid); CArray mb; CReal r (rc.xleft); mb.addProperty (r); r.setValue (rc.yleft); mb.addProperty (r); r.setValue (rc.xright); mb.addProperty (r); r.setValue (rc.yright); mb.addProperty (r); dictionary->setProperty ("MediaBox",mb); } // // Display a page // void CPage::displayPage (::OutputDev& out, shared_ptr pagedict, int x, int y, int w, int h) const { // Are we in valid pdf assert (hasValidPdf (dictionary)); assert (hasValidRef (dictionary)); assert (valid); if (!hasValidPdf (dictionary) || !hasValidRef (dictionary)) throw XpdfInvalidObject (); // Get xref XRef* xref = dictionary->getPdf()->getCXref (); assert (NULL != xref); // // Create xpdf object representing CPage // if (!(pagedict)) pagedict = dictionary; shared_ptr xpdfPage (pagedict->_makeXpdfObject(), xpdf::object_deleter()); // Check page dictionary assert (objDict == xpdfPage->getType ()); if (objDict != xpdfPage->getType ()) throw XpdfInvalidObject (); // Get page dictionary Dict* xpdfPageDict = xpdfPage->getDict (); assert (NULL != xpdfPageDict); // Init xpdf xpdf::openXpdfMess (); // // We need to handle special case // SplashOutputDev* sout = dynamic_cast (&out); if (sout) sout->startDoc (xref); // // Create default page attributes and make page // ATTRIBUTES are deleted in Page destructor // Page page (xref, 0, xpdfPageDict, new PageAttrs (NULL, xpdfPageDict)); // Create catalog scoped_ptr xpdfCatalog (new Catalog (xref)); // // Page object display (..., useMediaBox, crop, links, catalog) // int rotation = lastParams.rotate - getRotation (); page.displaySlice (&out, lastParams.hDpi, lastParams.vDpi, rotation, lastParams.useMediaBox, lastParams.crop,x,y,w,h, NULL, xpdfCatalog.get()); // // Cleanup // // Clean xpdf mess xpdf::closeXpdfMess (); } // // // void CPage::displayPage (::OutputDev& out, const DisplayParams& params, int x, int y, int w, int h) { setDisplayParams (params); displayPage (out,x,y,w,h); } void CPage::displayPage (::OutputDev& out, int x, int y, int w, int h) { assert (valid); // display page displayPage (out,dictionary,x,y,w,h); } // // Helper methods // // // // void CPage::createXpdfDisplayParams (boost::shared_ptr& res, boost::shared_ptr& state) { // // Init Gfx resources // // Init mess xpdf::openXpdfMess (); // Get resource dictionary InheritedPageAttr atr; fillInheritedPageAttr (dictionary,atr); // Start the resource stack XRef* xref = dictionary->getPdf()->getCXref(); assert (xref); Object* obj = atr.resources->_makeXpdfObject (); assert (obj); assert (objDict == obj->getType()); res = boost::shared_ptr (new GfxResources(xref, obj->getDict(), NULL)); freeXpdfObject (obj); // // Init Gfx state // // Create Media (Bounding) box shared_ptr rc (new PDFRectangle (lastParams.pageRect.xleft, lastParams.pageRect.yleft, lastParams.pageRect.xright, lastParams.pageRect.yright)); state = shared_ptr (new GfxState (lastParams.hDpi, lastParams.vDpi, rc.get(), lastParams.rotate, lastParams.upsideDown)); // Close the mess xpdf::closeXpdfMess (); } // // // bool CPage::parseContentStream () { assert (valid); // Clear content streams contentstreams.clear(); assert (hasValidRef(dictionary)); assert (hasValidPdf (dictionary)); if (!hasValidPdf(dictionary) || !hasValidRef(dictionary)) throw CObjInvalidObject (); // // Create state and resources // boost::shared_ptr res; boost::shared_ptr state; createXpdfDisplayParams (res, state); // // Get the stream representing content stream (if any), make an xpdf object // and finally instantiate CContentStream // if (!dictionary->containsProperty ("Contents")) return true; shared_ptr contents = getReferencedObject (dictionary->getProperty ("Contents")); assert (contents); CContentStream::CStreams streams; // // Contents can be either stream or an array of streams // if (isStream (contents)) { shared_ptr stream = IProperty::getSmartCObjectPtr (contents); streams.push_back (stream); }else if (isArray (contents)) { // We can be sure that streams are indirect objects (pdf spec) shared_ptr array = IProperty::getSmartCObjectPtr (contents); for (size_t i = 0; i < array->getPropertyCount(); ++i) streams.push_back (getCStreamFromArray(array,i)); }else // Neither stream nor array { kernelPrintDbg (debug::DBG_CRIT, "Content stream type: " << contents->getType()); throw ElementBadTypeException ("Bad content stream type."); } // // Create content streams, each cycle will take one/more content streams from streams variable // assert (contentstreams.empty()); // True if Contents is not [ ] //assert (!streams.empty()); while (!streams.empty()) { shared_ptr cc (new CContentStream(streams,state,res)); // Save smart pointer of the content stream so pdfoperators can return it cc->setSmartPointer (cc); contentstreams.push_back (cc); } // Everything went ok return true; } // // Contents Observer interface // // // // void CPage::registerContentsObserver () const { assert (valid); if (contentsWatchDog) { // Register contents observer if (dictionary->containsProperty("Contents")) { // Register dictionary and Contents observer dictionary->registerObserver (contentsWatchDog); dictionary->getProperty("Contents")->registerObserver (contentsWatchDog); } }else { assert (!"Observer is not initialized."); throw CObjInvalidOperation (); } } // // // void CPage::unregisterContentsObserver () const { assert (valid); // If contentsWatchDog is null it can mean that Contents entry was removed if (contentsWatchDog) { // Unregister dictionary observer dictionary->unregisterObserver (contentsWatchDog); // Unregister contents observer if (dictionary->containsProperty("Contents")) dictionary->getProperty("Contents")->unregisterObserver (contentsWatchDog); }else { assert (!"Observer is not initialized."); throw CObjInvalidOperation (); } } // // // void CPage::reparseContentStream ( ) { assert (valid); assert (hasValidRef(dictionary)); assert (hasValidPdf (dictionary)); if (!hasValidPdf(dictionary) || !hasValidRef(dictionary)) throw CObjInvalidObject (); // // Create state and resources // boost::shared_ptr res; boost::shared_ptr state; createXpdfDisplayParams (res, state); // Set only bboxes for (ContentStreams::iterator it = contentstreams.begin(); it != contentstreams.end(); ++it) (*it)->reparse (true, state, res); } // // Text search/find // // // Find all occcurences of a text on a page // template size_t CPage::findText (std::string text, RectangleContainer& recs, const TextSearchParams&) const { // Create text output device boost::scoped_ptr textDev (new ::TextOutputDev (NULL, gFalse, gFalse, gFalse)); assert (textDev->isOk()); if (!textDev->isOk()) throw CObjInvalidOperation (); // Get the text displayPage (*textDev); GBool startAtTop, stopAtBottom, startAtLast, stopAtLast, caseSensitive, backward; startAtTop = stopAtBottom = startAtLast = stopAtLast = gTrue; caseSensitive = backward = gFalse; double xMin = 0, yMin = 0, xMax = 0, yMax = 0; // Convert text to unicode (slightly modified from from PDFCore.cc) int length = text.length(); ::Unicode* utext = static_cast (new Unicode [length]); for (int i = 0; i < length; ++i) utext[i] = static_cast (text[i] & 0xff); if (textDev->findText (utext, length, startAtTop, stopAtBottom, startAtLast,stopAtLast, caseSensitive, backward, &xMin, &yMin, &xMax, &yMax)) { startAtTop = gFalse; recs.push_back (Rectangle (xMin, yMin, xMax, yMax)); // Get all text objects while (textDev->findText (utext, length, startAtTop, stopAtBottom, startAtLast, stopAtLast, caseSensitive, backward, &xMin, &yMin, &xMax, &yMax)) { recs.push_back (Rectangle (xMin, yMin, xMax, yMax)); } } // // Find out the words... // delete[] utext; return recs.size(); } // Explicit instantiation template size_t CPage::findText > (std::string text, std::vector& recs, const TextSearchParams& params) const; // // Font interface // // // // void CPage::addSystemType1Font (const std::string& fontname, bool winansienc) { // Create font dictionary // << // /Type /Font // /Subtype /Type1 // /BaseFont / ... // >> boost::shared_ptr font (new CDict ()); boost::shared_ptr name (new CName ("Font")); font->addProperty (string("Type"), *name); name->setValue ("Type1"); font->addProperty (string ("Subtype"), *name); name->setValue (fontname); font->addProperty (string ("BaseFont"), *name); // For accents if (winansienc) { name->setValue ("WinAnsiEncoding"); font->addProperty (string ("Encoding"), *name); } // Resources is an inheritable property, must be present if (!dictionary->containsProperty ("Resources")) { InheritedPageAttr atr; fillInheritedPageAttr (dictionary,atr); dictionary->addProperty ("Resources", *(atr.resources)); } // Get "Resources" boost::shared_ptr res = getCDictFromDict (dictionary, "Resources"); if (!res->containsProperty ("Font")) { boost::shared_ptr fontdict (new CDict ()); res->addProperty ("Font", *fontdict); } // Get "Fonts" boost::shared_ptr fonts = getCDictFromDict (res, "Font"); // Get all avaliable fonts typedef vector > Fonts; Fonts fs; getFontIdsAndNames (fs); // // Find suitable name // size_t len = 0; bool our = false; string ourfontname ("Fonticek"); for (Fonts::iterator it = fs.begin(); it != fs.end(); ++it) {// Compare basenames and look for longest string and for PdfEdit string if (ourfontname == (*it).first) our = true; len = std::max ((*it).first.length(), len); } // Make name unique if (our) {/**\todo make sane */ len -= ourfontname.length(); ++len; for (size_t i = 0; i < len; ++i) ourfontname.push_back ('k'); } // Add it fonts->addProperty (ourfontname, *font); // // Create state and resources and update our contentstreams // shared_ptr gfxres; shared_ptr gfxstate; createXpdfDisplayParams (gfxres, gfxstate); for (ContentStreams::iterator it = contentstreams.begin(); it != contentstreams.end(); ++it) (*it)->setGfxParams (gfxstate, gfxres); } // // // template void CPage::getFontIdsAndNames (Container& cont) const { // Clear container cont.clear (); InheritedPageAttr atr; fillInheritedPageAttr (dictionary, atr); boost::shared_ptr res = atr.resources; try { boost::shared_ptr fonts = utils::getCDictFromDict (res, "Font"); typedef std::vector FontNames; FontNames fontnames; // Get all font names (e.g. R14, R15, F19...) fonts->getAllPropertyNames (fontnames); // Get all base names (Symbol, csr12, ...) for (FontNames::iterator it = fontnames.begin(); it != fontnames.end(); ++it) { boost::shared_ptr font = utils::getCDictFromDict (fonts, *it); try { std::string fontbasename; if (font->containsProperty ("BaseFont")) // Type{1,2} font fontbasename = utils::getNameFromDict (font, "BaseFont"); else // TrueType font fontbasename = utils::getNameFromDict (font, "Subtype"); cont.push_back (std::make_pair (*it, fontbasename)); }catch (ElementNotFoundException&) {} } }catch (ElementNotFoundException&) { kernelPrintDbg (debug::DBG_ERR, "No resource dictionary."); } } template void CPage::getFontIdsAndNames > > (vector >& cont) const; // // Transform matrix // // // // void CPage::setTransformMatrix (double tm[6]) { assert (valid); if (contentstreams.empty()) { // Try parsing streams parseContentStream (); if (contentstreams.empty()) return; } assert (!contentstreams.empty()); shared_ptr str = contentstreams.front(); assert(str); // // Create new cm operator // PdfOperator::Operands operands; operands.push_back (shared_ptr (new CReal (tm[0]))); operands.push_back (shared_ptr (new CReal (tm[1]))); operands.push_back (shared_ptr (new CReal (tm[2]))); operands.push_back (shared_ptr (new CReal (tm[3]))); operands.push_back (shared_ptr (new CReal (tm[4]))); operands.push_back (shared_ptr (new CReal (tm[5]))); shared_ptr cmop (new SimpleGenericOperator ("cm", 6, operands)); // Insert at the beginning str->frontInsertOperator (cmop); } // // // void CPage::_objectChanged (bool invalid) { assert (valid); // Do not notify anything if we are not in a valid pdf if (!hasValidPdf (dictionary)) return; assert (hasValidRef (dictionary)); boost::shared_ptr current (this, EmptyDeallocator ()); // Notify observers if (invalid) this->notifyObservers (current, shared_ptr ()); else this->notifyObservers (current, shared_ptr (new BasicObserverContext (current))); } // ===================================================================================== namespace { // ===================================================================================== /** * Create stream from container of objects that implement * getStringRepresentation function. And inserts this stream into supplied pdf. */ template shared_ptr createStreamFromObjects (const Container& cont, CPdf& pdf) { // Create stream with one default property Length shared_ptr newstr (new CStream()); // Insert our change tag std::string str; { std::string tmpop; createChangeTag()->getStringRepresentation (tmpop); str += tmpop + " "; } // // Get string representation of new content stream // typename Container::const_iterator it = cont.begin(); for (; it != cont.end(); ++it) { std::string tmpop; (*it)->getStringRepresentation (tmpop); str += tmpop; } kernelPrintDbg (debug::DBG_DBG, str); // Set the stream newstr->setBuffer (str); // Set ref and indiref reserve free indiref for the new object IndiRef newref = pdf.addIndirectProperty (newstr); newstr = IProperty::getSmartCObjectPtr (pdf.getIndirectProperty (newref)); assert (newstr); return newstr; } /** * Get all cstreams from a container of content streams. */ template void getAllCStreams (const In& in, Out& out) { for (typename In::const_iterator it = in.begin(); it != in.end(); ++it) { Out tmp; (*it)->getCStreams (tmp); copy (tmp.begin(), tmp.end(), back_inserter (out)); } } /** * Helper class providing convinient access and modify operations on * "Contents" entry of a page dictionary. */ struct ContentsHandler { shared_ptr _dict; ContentsHandler (shared_ptr dict) : _dict(dict) {} void toFront (CRef& ref) { if (!_dict->containsProperty ("Contents")) { CArray arr; arr.addProperty (ref); _dict->addProperty ("Contents", arr); }else { shared_ptr content = _dict->getProperty ("Contents"); shared_ptr realcontent = getReferencedObject(content); assert (content); // Contents can be either stream or an array of streams if (isStream (realcontent)) { CArray arr; arr.addProperty (ref); arr.addProperty (*content); _dict->setProperty ("Contents", arr); }else if (isArray (realcontent)) { // We can be sure that streams are indirect objects (pdf spec) shared_ptr array = IProperty::getSmartCObjectPtr (realcontent); array->addProperty (0, ref); }else // Neither stream nor array { kernelPrintDbg (debug::DBG_CRIT, "Content stream type: " << realcontent->getType()); throw ElementBadTypeException ("Bad content stream type."); } } } void toBack (CRef& ref) { if (!_dict->containsProperty ("Contents")) { CArray arr; arr.addProperty (ref); _dict->addProperty ("Contents", arr); }else { shared_ptr content = _dict->getProperty ("Contents"); shared_ptr realcontent = getReferencedObject(content); assert (content); // Contents can be either stream or an array of streams if (isStream (realcontent)) { CArray arr; arr.addProperty (*content); arr.addProperty (ref); _dict->setProperty ("Contents", arr); }else if (isArray (realcontent)) { // We can be sure that streams are indirect objects (pdf spec) shared_ptr array = IProperty::getSmartCObjectPtr (realcontent); array->addProperty (array->getPropertyCount(), ref); }else // Neither stream nor array { kernelPrintDbg (debug::DBG_CRIT, "Content stream type: " << realcontent->getType()); throw ElementBadTypeException ("Bad content stream type."); } } } /** * Set Contents entry from a container of content streams. */ template void setContents (const Cont& cont) { if (_dict->containsProperty ("Contents")) _dict->delProperty ("Contents"); // // Loop throug all content streams and add all cstreams from each // content streams to "Contents" entry of page dictionary // typedef vector > Css; Css css; getAllCStreams (cont, css); // Loop through all cstreams for (Css::iterator it = css.begin(); it != css.end(); ++it) { assert (hasValidPdf (*it)); assert (hasValidRef (*it)); if (!hasValidPdf (*it) || !hasValidRef (*it)) throw XpdfInvalidObject (); CRef rf ((*it)->getIndiRef ()); toBack (rf); } } /** * Remove content streams references from Contents entry. */ void remove (shared_ptr cs) { if (!_dict->containsProperty ("Contents")) throw CObjInvalidOperation (); // // Loop throug all content streams and add all cstreams from each // content streams to "Contents" entry of page dictionary // typedef vector > Css; Css css; cs->getCStreams (css); // Loop through all cstreams for (Css::iterator it = css.begin(); it != css.end(); ++it) { assert (hasValidPdf (*it)); assert (hasValidRef (*it)); if (!hasValidPdf (*it) || !hasValidRef (*it)) throw CObjInvalidObject (); // Remove the reference remove ((*it)->getIndiRef ()); } } private: /** * Remove one indiref from Contents entry. */ void remove (const IndiRef& rf) { shared_ptr content = _dict->getProperty ("Contents"); shared_ptr realcontent = getReferencedObject (content); assert (content); // Contents can be either stream or an array of streams if (isStream (realcontent)) { // Set empty contents CArray arr; _dict->setProperty ("Contents", arr); }else if (isArray (realcontent)) { // We can be sure that streams are indirect objects (pdf spec) shared_ptr array = IProperty::getSmartCObjectPtr (realcontent); for (size_t i = 0; i < array->getPropertyCount(); ++i) { IndiRef _rf = getRefFromArray (array,i); if (_rf == rf) { array->delProperty (i); return; } } }else // Neither stream nor array { kernelPrintDbg (debug::DBG_CRIT, "Content stream type: " << realcontent->getType()); throw ElementBadTypeException ("Bad content stream type."); } } }; // struct ContentsHandler // ===================================================================================== } // namespace // ===================================================================================== // // // template void CPage::addContentStreamToFront (const Container& cont) { // Create cstream from container of pdf operators CPdf* pdf = dictionary->getPdf(); assert (pdf); shared_ptr stream = createStreamFromObjects (cont, *pdf); assert (hasValidRef (stream)); assert (hasValidPdf (stream)); if (!hasValidPdf(stream) || !hasValidPdf(stream)) throw CObjInvalidObject (); // Change the contents entry unregisterContentsObserver (); ContentsHandler cnts (dictionary); CRef ref (stream->getIndiRef()); cnts.toFront (ref); registerContentsObserver (); // Parse new stream to content stream and add it to the streams CContentStream::CStreams streams; streams.push_back (stream); boost::shared_ptr res; boost::shared_ptr state; createXpdfDisplayParams (res, state); ContentStreams _tmp; boost::shared_ptr cc (new CContentStream(streams,state,res)); // Save smart pointer of the content stream so pdfoperators can return it cc->setSmartPointer (cc); _tmp.push_back (cc); std::copy (contentstreams.begin(), contentstreams.end(), std::back_inserter(_tmp)); contentstreams = _tmp; // Indicate change _objectChanged (); } template void CPage::addContentStreamToFront > > (const vector >& cont); template void CPage::addContentStreamToFront > > (const deque >& cont); // // // template void CPage::addContentStreamToBack (const Container& cont) { // Create cstream from container of pdf operators CPdf* pdf = dictionary->getPdf(); assert (pdf); shared_ptr stream = createStreamFromObjects (cont, *pdf); assert (hasValidRef (stream)); assert (hasValidPdf (stream)); if (!hasValidPdf(stream) || !hasValidPdf(stream)) throw CObjInvalidObject (); // Change the contents entry unregisterContentsObserver (); ContentsHandler cnts (dictionary); CRef ref (stream->getIndiRef()); cnts.toBack (ref); registerContentsObserver (); // Parse new stream to content stream and add it to the streams CContentStream::CStreams streams; streams.push_back (stream); boost::shared_ptr res; boost::shared_ptr state; createXpdfDisplayParams (res, state); boost::shared_ptr cc (new CContentStream(streams,state,res)); // Save smart pointer cc->setSmartPointer (cc); contentstreams.push_back (cc); // Indicate change _objectChanged (); } template void CPage::addContentStreamToBack > > (const vector >& cont); template void CPage::addContentStreamToBack > > (const deque >& cont); // // // void CPage::removeContentStream (size_t csnum) { // Create cstream from container of pdf operators CPdf* pdf = dictionary->getPdf(); assert (pdf); if (csnum >= contentstreams.size()) throw OutOfRange (); // Change the contents entry unregisterContentsObserver (); ContentsHandler cnts (dictionary); cnts.remove (contentstreams[csnum]); registerContentsObserver (); // Remove contentstream from container contentstreams.erase (contentstreams.begin() + csnum, contentstreams.begin() + csnum + 1); // Indicate change _objectChanged (); } // // Our changes // // ===================================================================================== namespace { // ===================================================================================== /** Sort according to the time of change. * Least means the change was the last one. */ struct ccs_change_sorter { bool operator() (shared_ptr frst, shared_ptr scnd) { typedef vector > Ops; static const bool FIRST_IS_OUR_LAST = true; static const bool SECOND_IS_OUR_LAST = false; Ops opFrst, opScnd; frst->getPdfOperators (opFrst); scnd->getPdfOperators (opScnd); assert (!opFrst.empty()); assert (!opScnd.empty()); ChangePdfOperatorIterator itFrst = PdfOperator::getIterator (opFrst.front()); ChangePdfOperatorIterator itScnd = PdfOperator::getIterator (opScnd.front()); assert (itFrst.valid()); assert (itScnd.valid()); time_t tmfrst = getChangeTagTime (itFrst.getCurrent()); time_t tmscnd = getChangeTagTime (itScnd.getCurrent()); if (tmfrst > tmscnd) return FIRST_IS_OUR_LAST; else return SECOND_IS_OUR_LAST; } }; // ===================================================================================== } // namespace // ===================================================================================== // // // template void CPage::getChanges (Container& cont) const { cont.clear(); for (ContentStreams::const_iterator it = contentstreams.begin(); it != contentstreams.end(); ++it) { typedef vector > Ops; Ops ops; (*it)->getPdfOperators (ops); // Empty contentstream is not our change if (ops.empty()) continue; ChangePdfOperatorIterator chng = PdfOperator::getIterator (ops.front()); // Not containing our change tag meaning not our change if (!chng.valid()) continue; cont.push_back (*it); } sort (cont.begin(), cont.end(), ccs_change_sorter()); } template void CPage::getChanges > > (vector >& cont) const; // // // shared_ptr CPage::getChange (size_t nthchange) const { typedef vector > CCs; CCs ccs; getChanges (ccs); if (nthchange >= ccs.size()) throw OutOfRange (); return ccs[nthchange]; } // // // size_t CPage::getChangeCount () const { size_t cnt = 0; for (ContentStreams::const_iterator it = contentstreams.begin(); it != contentstreams.end(); ++it) { typedef vector > Ops; Ops ops; (*it)->getPdfOperators (ops); // Empty contentstream is not our change if (ops.empty()) continue; ChangePdfOperatorIterator chng = PdfOperator::getIterator (ops.front()); // Not containing our change tag meaning not our change if (!chng.valid()) continue; // Found another change ++cnt; } return cnt; } // // // template void CPage::displayChange (::OutputDev& out, const Container& cont) const { shared_ptr fakeDict (IProperty::getSmartCObjectPtr(dictionary->clone())); assert (fakeDict); ContentsHandler hnd (fakeDict); hnd.setContents (cont); // // Beware: from this point, fakeDict is not changed so xpdf can be set // otherwise the condition that when pdf is valid ref must be also is not // met // if (hasValidPdf (dictionary)) fakeDict->setPdf(dictionary->getPdf()); // Display page using our dictionary displayPage (out, fakeDict); } template void CPage::displayChange > > (::OutputDev& out, const vector >& cont) const; // // // void CPage::moveAbove (shared_ptr ct) { // Get the next item ContentStreams::iterator itNext = find (contentstreams.begin(), contentstreams.end(), ct); if (itNext == contentstreams.end()) throw CObjInvalidOperation (); ++itNext; if (itNext == contentstreams.end()) throw OutOfRange (); // Delete next item but store it shared_ptr tmp = *itNext; contentstreams.erase (itNext, itNext + 1); // Insert stored item before supplied (simply swap ct with the next item) contentstreams.insert (find (contentstreams.begin(), contentstreams.end(), ct), tmp); // Also change Contents entry of page dictionary unregisterContentsObserver (); ContentsHandler cnts (dictionary); cnts.setContents (contentstreams); registerContentsObserver (); // Indicate change _objectChanged (); } // // // void CPage::moveBelow (shared_ptr ct) { // Get the item index unsigned int pos = 0; for (pos = 0; pos < contentstreams.size(); ++pos) if (contentstreams[pos] == ct) break; // If first or not found throw exception if (pos == contentstreams.size() || 0 == pos) throw CObjInvalidOperation (); assert (0 < pos); // Swap shared_ptr tmp = contentstreams[pos]; contentstreams[pos] = contentstreams[pos - 1]; contentstreams[pos - 1] = tmp; // Also change Contents entry of page dictionary unregisterContentsObserver (); ContentsHandler cnts (dictionary); cnts.setContents (contentstreams); registerContentsObserver (); // Indicate change _objectChanged (); } // // Page position // size_t CPage::getPagePosition () const { if (hasValidPdf (dictionary)) return dictionary->getPdf()->getPagePosition (shared_ptr (const_cast(this),EmptyDeallocator ())); throw CObjInvalidOperation (); } // ===================================================================================== // Helper functions // ===================================================================================== // // // bool isPage (boost::shared_ptr ip) { assert (ip); assert (isDict (ip)); if (!isDict(ip)) throw CObjInvalidObject (); boost::shared_ptr dict = IProperty::getSmartCObjectPtr (ip); if ("Page" != getStringFromDict (dict, "Type")) throw CObjInvalidObject (); return true; } // ===================================================================================== } // namespace pdfobjects // =====================================================================================