/* * 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: ccontentstream.h * Created: 03/24/2006 10:34:03 PM CET * Author: jmisutka () * ===================================================================================== */ #ifndef _CCONTENTSTREAM_H_ #define _CCONTENTSTREAM_H_ // static includes #include "static.h" // #include "cobject.h" // PdfOperator and its iterators #include "pdfoperators.h" #include "pdfoperatorsiter.h" //========================================================== namespace pdfobjects { //========================================================== // // Forward declaration // class IProperty; class CContentStream; typedef observer::ObserverHandler CContentStreamObserverSubject; typedef observer::IObserver IIPropertyObserver; //========================================================== // CContentStream //========================================================== /** * Content stream class representing pdf content stream. * * This object represents operators and operands of a content stream. Each content stream consists of * one or more indirect streams. These streams are listed in "Contents" entry of page dictionary. * * Each operator represents an operation that alters the current graphic state. * These operators are processed sequentially. We cannot parse only one stream * when the content stream consists of more streams, because the split point is * almost arbitrary. Every content stream class represents a valid content which * can consist of one or more streams. * * During initialization of this object, observers are registred on all * underlying streams, which means a change to the stream object(s) representing * the content stream will transparently update this object. This feature could be * tricky. (E.g. Content stream consists of more streams and we need * to save the content stream, which means changing streams one after another. * After a change the content stream becomes invalid.) * * Operators form a tree-like structure consisting of Simple and Composite objects. * * Only first level operators are stored. * * The pdf feature that a content stream can consist of several streams means we * can not derive from CStream object. Due to this limitation we do not have * Observer interface so we need to implement it. * * Mainly this object is responsible for all visible objects on a page. If a * content stream is empty, the page is empty. Changing operators can be very * destructive (e.g. text matrix, font type, deleting one operator of a pair, * ...) * * The requirement of processing operators sequentially has lead to the decision * that these operators will be in an iterator queue meaning we can process them * seqeuntially with the advantage of Iterator design pattern. * * Another characteristis of content stream operators is that they build a tree * like structure which has lead to another decision that these operators will * be in a tree like queue. It means operators are designed as a Composite * design pattern. This enables e.g. gui to represent content stream in a human * readable from) */ class CContentStream : public noncopyable, public CContentStreamObserverSubject { public: typedef std::list > Operators; typedef std::list > CStreams; typedef PdfOperator::Iterator OperatorIterator; typedef observer::BasicChangeContext BasicObserverContext; private: /** Underlying cstream objects. */ CStreams cstreams; /** Parsed first level content stream operators. */ Operators operators; /** Graphical state. */ boost::shared_ptr gfxstate; /** Graphical resources. */ boost::shared_ptr gfxres; /** Smart pointer to this object. */ boost::weak_ptr smart_this; // // Observer observing underlying cstreams and operands // private: /** * Content stream observer. * * After a cstream changes reparse the contentstream. * * It can happen that the stream is parsed also after page's Contents entry * has been modified in a way that this content stream no longer exists. It * depends on the position of this observer in cstream observer list. */ struct CStreamObserver : public IIPropertyObserver { // // Constructor // CStreamObserver (CContentStream* cc) : contentstream (cc) {assert (cc);} // // Observer interface // virtual void notify (boost::shared_ptr newValue, boost::shared_ptr context) const throw(); virtual priority_t getPriority() const throw () {return 0;} // // Destructor // virtual ~CStreamObserver () throw () {} private: CContentStream* contentstream; }; /** * Operand stream observer. * * If an operand is changed, save the stream notifying all observers. */ struct OperandObserver : public IIPropertyObserver { // // Constructor // OperandObserver (CContentStream* cc) : contentstream (cc) {assert (cc);} // // Observer interface // virtual void notify (boost::shared_ptr newValue, boost::shared_ptr) const throw(); virtual priority_t getPriority() const throw () {return 0;} // // Destructor // virtual ~OperandObserver () throw () {} private: CContentStream* contentstream; }; /** Observer observing underlying cstreams. */ boost::shared_ptr cstreamobserver; /** Observer observing operands of all operators. */ boost::shared_ptr operandobserver; // // Constructors // public: /** * Constructor. * * @param strs Array of content streams. Typically only one. * @param state Graphical state. * @param res Graphical resources. */ CContentStream (CStreams& strs, boost::shared_ptr state, boost::shared_ptr res); // // Get methods // public: /** * Get all cstreams from which this content stream is made of. */ template void getCStreams (Cont& cont) const { std::copy (cstreams.begin(), cstreams.end(), back_inserter(cont)); } /** * Get the string representation of specific operators. * * Traverse all operators using specific iterator and save their string * repersentation. * * REMARK: If an iterator accepts composite objects and also simple objects and if an accepted composite * contains one accepted simple object, the * string representation will be incorrect. It will contain the string * represenation of the composite including the child and also a separate * string representation of the child. * * @param str Output string. */ template void getStringRepresentation (std::string& str) const { kernelPrintDbg (debug::DBG_DBG, ""); if (operators.empty ()) return; // Clear string str.clear (); // Loop through every operator Iter it = PdfOperator::getIterator (operators.front()); while (!it.isEnd()) { std::string tmp; it.getCurrent()->getStringRepresentation (tmp); str += tmp + " "; it.next(); } } /** * Get the string representation of the content stream. * * @param str Output string. */ void getStringRepresentation (std::string& str) const { utilsPrintDbg (debug::DBG_DBG, ""); if (operators.empty ()) return; // Clear string str.clear (); // Loop through every operator for (Operators::const_iterator it = operators.begin(); it != operators.end(); ++it) { std::string tmp; (*it)->getStringRepresentation (tmp); str += tmp + " "; } } /** * Get objects at position. * * We can compute a bounding box (rectangle) to every operator specifying its exact position. * This bounding box is used to select specific operators. * * @param opContainer Output container. * @param cmp Comparator that will decide if an operator is close enough. */ template void getOperatorsAtPosition (OpContainer& opContainer, const PdfOpPosComparator& cmp) const { utilsPrintDbg (debug::DBG_DBG, ""); if (operators.empty()) return; ChangeableOperatorIterator it = PdfOperator::getIterator (operators.front()); while (!it.isEnd()) { if (cmp(it.getCurrent()->getBBox())) opContainer.push_back (boost::shared_ptr (it.getCurrent())); // debug std::string tmp; it.getCurrent()->getOperatorName (tmp); utilsPrintDbg (debug::DBG_DBG, tmp << " " << it.getCurrent()->getBBox()); // assert (!it.isEnd()); it.next(); } // DEBUG OUTPUT utilsPrintDbg (debug::DBG_DBG, "--- SELECTED THESE OPERATORS --- "); for (typename OpContainer::const_iterator it = opContainer.begin (); it != opContainer.end(); ++it) { PdfOperator::Operands ops; (*it)->getParameters (ops); std::string strop; if (0 < ops.size()) ops[0]->getStringRepresentation (strop); std::string tmp; (*it)->getOperatorName (tmp); utilsPrintDbg (debug::DBG_DBG, tmp << "(" << strop << "): " << (*it)->getBBox()); } utilsPrintDbg (debug::DBG_DBG, "--------------------------------- "); // } /** * Get first level pdf operators. * * Operators form a tree-like structure. We store all root operands in a * container. * * @param container Output container. */ template void getPdfOperators (T& container) const { container.clear (); std::copy (operators.begin(), operators.end(), std::back_inserter (container)); } // // Change methods // public: /** * Delete an operator from a content stream. * * We have to remove an operator from the iterator queue and also from the tree queue. * * When removing from the tree queue an operator we have to find it and remove * from the composite it is in (if not first level) * * Needs to be careful when removing from the iterator queue. If a composite is removed * we have to update its top level operator and also the last item of the * iterator queue that is still in the composite. * * @param it Iterator pointing to operator that will be deleted. * @param indicateChange If true, changed contentstream will be written to its cstreams, * otherwise the change will not be visible. */ void deleteOperator (OperatorIterator it, bool indicateChange = true); void deleteOperator (boost::shared_ptr oper, bool indicateChange = true) { deleteOperator (PdfOperator::getIterator (oper), indicateChange); } /** * Insert pdf operator after specified position. If it is not valid it is * assumend that the content stream is empty and operator will be inserted * at the beginning. * * We have to insert the operator into the iterator queue and also into the tree queue. * * Insertion has to take the depth of the position into account. * * @param it Iterator pointing to operator after which the new operator will be inserted. * @param newOper Operator that will be inserted. * @param indicateChange If true, changed contentstream will be written to its cstreams, * otherwise the change will not be visible. */ void insertOperator (OperatorIterator it, boost::shared_ptr newOper, bool indicateChange = true); /** \see insertOperator */ void insertOperator (boost::shared_ptr oper, boost::shared_ptr newOper, bool indicateChange = true) { insertOperator (PdfOperator::getIterator (oper), newOper, indicateChange); } /** * Insert pdf operator in the front. * * @param newOper Operator that will be inserted. * @param indicateChange If true, changed contentstream will be written to its cstreams, * otherwise the change will not be visible. */ void frontInsertOperator (boost::shared_ptr newOper, bool indicateChange = true); /** * Replace an operator with another one. * * We have to modify both iterator queue and tree queue. * * This can be a problem. When replacing an operator with another operator * which contains the same instance of the original operator (meaning * the new operator is a composite) we can not use * iterator queue of the original, because it was altered when inserting it * into the new operator and points to items in the new operator. * * All change functions behave this way. They insert * the original operator in a new composite altering the iterator queue and * making the content stream iterator queue invalid. That is why we need * the next and previous items of the original operator as function * arguments. * * @param it Iterator pointing to the element that will be replaced. * @param newOper New operator. * @param indicateChange If true, changed contentstream will be written to its cstreams, * otherwise the change will not be visible. */ void replaceOperator (OperatorIterator it, boost::shared_ptr newOper, bool indicateChange = true); void replaceOperator (boost::shared_ptr oper, boost::shared_ptr newOper, bool indicateChange = true) { replaceOperator (PdfOperator::getIterator (oper), newOper, indicateChange); } // // Helper methods // public: /** * Is the content stream empty. * * @return True if the contentstream is empty, false otherwise. */ bool empty () const {return operators.empty ();} /** * Reparse pdf operators and set their bounding boxes. * * @param bboxOnly If true only bounding boxes are set, if false operators * are also reparsed. * @param state Graphical state, if changed. * @param res Graphical resources, if changed. */ void reparse (bool bboxOnly = false, boost::shared_ptr state = boost::shared_ptr (), boost::shared_ptr res = boost::shared_ptr ()); /** * Set gfx resources. */ void setGfxParams (boost::shared_ptr state, boost::shared_ptr res) { gfxstate = state; gfxres = res; } /** * Save content stream to underlying cstream(s) and notify all observers. * * Does not reparse anything. */ void saveChange () { _objectChanged(); } /** * Get smart pointer to this content stream. * * @return Smart pointer to this content stream. */ boost::shared_ptr getSmartPointer () const { assert (!smart_this.expired()); return smart_this.lock(); } /** * Set smart pointer to this content stream. * * @param ths Smart pointer to this content stream. */ void setSmartPointer (boost::shared_ptr ths) { assert (smart_this.expired()); smart_this = ths; } private: /** * Save changes and indicate that the object has changed by calling all * observers. */ void _objectChanged (); // // Observers // protected: /** * Register observers on all cstreams that this object consists of. * * This function is called in constructor and also after saving to more * cstreams. */ void registerCStreamObservers () const; /** * Unregister observers from all cstreams that this object consists of. * * This function is called when saving consten stream consisting of * more streams. If we do not unregister observers, we would be notified * that a stream has changed after the first save (when the content stream * is invalid) and our observer would want to reparse an invalid stream. */ void unregisterCStreamObservers () const; // // Destructor // public: /** Destructor. */ ~CContentStream () { kernelPrintDbg (debug::DBG_DBG, "destructing.."); // Unregister cstream observers unregisterCStreamObservers (); } }; //========================================================== // Operator changing functions //========================================================== // // All changing operators have one drawback. They have to know if they change // "nonstroking" operations or "stroking" operations. // /** * Tries to find first non stroking operator. * * Some operators are modified by stroking operators some by nonstroking. * (e.g. when changing color, we can change color either of a stroking operator using RG * operator or of a nonstroking operator using rg operator. RG operator does not * change color of nonstroking operators.) * * @param oper Pdf operator that will be searched for a non stroking * operator. * * @return True if found, false otherwise. */ bool containsNonStrokingOperator (boost::shared_ptr oper); /** * Tries to find first stroking operator. * * Some operators are modified by stroking operators some by nonstroking. * (e.g. when changing color, we can change color either of a stroking operator using RG * operator or of a nonstroking operator using rg operator. RG operator does not * change color of nonstroking operators.) * * @param oper Pdf operator that will be searched for a stroking operator. * * @return True if found, false otherwise. */ bool containsStrokingOperator (boost::shared_ptr oper); //========================================================== } // namespace pdfobjects //========================================================== #endif // _CCONTENTSTREAM_H_