/* * Copyright (C) 2012 Webdoc SA * * This file is part of Open-Sankoré. * * Open-Sankoré 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, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * * Open-Sankoré 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 Open-Sankoré. If not, see . */ #include "Page.h" #include "Document.h" #include "ContentHandler.h" #include "Exception.h" #include "MediaBoxElementHandler.h" #include "CropBoxElementHandler.h" #include "TypeElementHandler.h" #include "RemoveHimSelfHandler.h" #include "AnnotsHandler.h" #include "RotationHandler.h" #include "FlateDecode.h" #include "Utils.h" #include "Rectangle.h" #include "Filter.h" #include #include #include "Parser.h" #include "core/memcheck.h" using namespace merge_lib; Page::Page(unsigned int pageNumber): _root(NULL),_pageNumber(pageNumber), _rotation(0) { } Page::~Page() { } std::string & Page::getPageContent() { return _root->getObjectContent(); } const Object::Children & Page::getPageRefs() { return _root->getChildren(); } void Page::recalculateObjectNumbers(unsigned int & newNumber) { _root->recalculateObjectNumbers(newNumber); } Object * Page::pageToXObject(std::vector & allObjects, std::vector & annots, bool isCloneNeeded) { Object * xObject = (isCloneNeeded) ? _root->getClone(allObjects) : _root; return _pageToXObject(xObject, annots); } Object * Page::_pageToXObject(Object *& page, std::vector & annots) { RemoveHimselfHandler * removeParent = new RemoveHimselfHandler(page, "/Parent"); RemoveHimselfHandler * removeBleedBox = new RemoveHimselfHandler(page, "/BleedBox"); RemoveHimselfHandler * removeTrimBox = new RemoveHimselfHandler(page, "/TrimBox"); RemoveHimselfHandler * removeArtBox = new RemoveHimselfHandler(page, "/ArtBox"); RemoveHimselfHandler * removeBoxColorInfo = new RemoveHimselfHandler(page, "/BoxColorInfo"); RemoveHimselfHandler * removeRotate = new RemoveHimselfHandler(page, "/Rotate"); RemoveHimselfHandler * removeThumb = new RemoveHimselfHandler(page, "/Thumb"); RemoveHimselfHandler * removeB = new RemoveHimselfHandler(page, "/B"); RemoveHimselfHandler * removeDur = new RemoveHimselfHandler(page, "/Dur"); RemoveHimselfHandler * removeTrans = new RemoveHimselfHandler(page, "/Trans"); AnnotsHandler * removeAnnots = new AnnotsHandler(page, "/Annots", annots); RemoveHimselfHandler * removeAA = new RemoveHimselfHandler(page, "/AA"); RemoveHimselfHandler * removeID = new RemoveHimselfHandler(page, "/ID"); RemoveHimselfHandler * removePZ = new RemoveHimselfHandler(page, "/PZ"); RemoveHimselfHandler * removeSeparationInfo = new RemoveHimselfHandler(page, "/SeparationInfo"); RemoveHimselfHandler * removeTabs = new RemoveHimselfHandler(page, "/Tabs"); RemoveHimselfHandler * removeTemplateInstantiated = new RemoveHimselfHandler(page, "/TemplateInstantiated"); RemoveHimselfHandler * removePresSteps = new RemoveHimselfHandler(page, "/PresSteps"); RemoveHimselfHandler * removeUserUnit = new RemoveHimselfHandler(page, "/UserUnit"); RemoveHimselfHandler * removeVP = new RemoveHimselfHandler(page, "/VP"); ContentHandler * contentHandler = new ContentHandler(page, "/Contents"); CropBoxElementHandler * cropBoxElementHandler = new CropBoxElementHandler(page); MediaBoxElementHandler * mediaBoxElementHandler = new MediaBoxElementHandler(page); TypeElementHandler * typeElementHandler = new TypeElementHandler(page); cropBoxElementHandler->addNextHandler(mediaBoxElementHandler); mediaBoxElementHandler->addNextHandler(removeParent); removeParent->addNextHandler(removeBleedBox); removeBleedBox->addNextHandler(removeTrimBox); removeTrimBox->addNextHandler(removeArtBox); removeArtBox->addNextHandler(removeBoxColorInfo); removeBoxColorInfo->addNextHandler(removeRotate); removeRotate->addNextHandler(removeThumb); removeThumb->addNextHandler(removeB); removeB->addNextHandler(removeDur); removeDur->addNextHandler(removeTrans); removeTrans->addNextHandler(removeAnnots); removeAnnots->addNextHandler(removeAA); removeAA->addNextHandler(removeID); removeID->addNextHandler(removePZ); removePZ->addNextHandler(removeSeparationInfo); removeSeparationInfo->addNextHandler(removeTabs); removeTabs->addNextHandler(removeTemplateInstantiated); removeTemplateInstantiated->addNextHandler(removePresSteps); removePresSteps->addNextHandler(removeUserUnit); removeUserUnit->addNextHandler(removeVP); removeVP->addNextHandler(typeElementHandler); typeElementHandler->addNextHandler(contentHandler); cropBoxElementHandler->processObjectContent(); cropBoxElementHandler->changeObjectContent(); delete cropBoxElementHandler; return page; } std::string _getContentOfContentObject(MergePageDescription & description) { std::string content("<<\n/Length "); std::string stream = ""; if(!description.skipBasePage) { stream.append("1.000000 0 0 1.000000 0 0 cm\n" "q\n"); stream.append(description.basePageTransformation.getCMT()); stream.append("/OriginalPage2 Do\n" "Q\n"); } if(!description.skipOverlayPage) { stream.append("1.000000 0 0 1.000000 0 0 cm\n" "q\n"); stream.append(description.overlayPageTransformation.getCMT()); stream.append("/OriginalPage1 Do\n" "Q\n"); } FlateDecode encoder; encoder.encode(stream); content.append(Utils::uIntToStr(stream.size())); content.append("\n/Filter /FlateDecode\n>>\nstream\n"); content.append(stream); content.append("endstream\n"); return content; } void _recalculateAnnotsCoordinates(Object * annotation, const Rectangle & basePagesRectangle, const Rectangle & outputPagesRectangle, const MergePageDescription & description) { Q_UNUSED(outputPagesRectangle); Q_UNUSED(basePagesRectangle); std::string annotsRectangleName("/Rect"); Object * objectWithRectangle; unsigned int fake; annotation->findObject(annotsRectangleName, objectWithRectangle, fake); std::string annotContent = objectWithRectangle->getObjectContent(); Rectangle annotsRectangle(annotsRectangleName.c_str(), annotContent); //we move annotation from base page to output page //that's way annotation should be scaled before all transformations. //Annotation's coordinates should be recalculated according to new //page width and height annotsRectangle.recalculateInternalRectangleCoordinates(description.basePageTransformation.getAnnotsTransformations()); annotsRectangle.updateRectangle(annotation, " "); } // function updates parent reference of annotation with new page object static void _updateAnnotParentPage(Object *annotation,Object *newParentPage) { if( annotation ) { std::string strP = "/P"; std::string &annotContent = annotation->getObjectContent(); size_t startOfP = Parser::findTokenName(annotContent,strP); if((int) startOfP == -1 ) { return; } size_t endOfP = Parser::findEndOfElementContent(annotContent,startOfP + strP.size()); // lets find object with reference to parent std::vector children = annotation->getChildrenByBounds(startOfP,endOfP); if( children.size() == 0 ) { return; } Object *childWithP = children[0]; if( childWithP ) { Object::ReferencePositionsInContent pagePosition = annotation->removeChild(childWithP); annotation->eraseContent(startOfP,endOfP-startOfP); std::stringstream strout; strout <<"/P "<getObjectNumber()<<" "<getgenerationNumber()<<" R\n"; // to compensate posible deviation for(size_t i =strout.str().size();iinsertToContent(startOfP,strout.str()); annotation->addChild(newParentPage,pagePosition); } } } // function performs adjusting of some color parameters of annotation // to avoid interference with overlay content static void _updateAnnotFormColor(Object *annotation ) { std::string &objectContent = annotation->getObjectContent(); if((int) objectContent.find("/Widget") == -1 ) { return; } size_t startOfAP = Parser::findTokenName(objectContent,"/AP"); if((int) startOfAP == -1 ) { return; } size_t endOfAP = objectContent.find(">>", startOfAP); std::vector aps = annotation->getChildrenByBounds(startOfAP, endOfAP); for(size_t i = 0; i < aps.size(); ++i) { Object * childWithAP = aps[i]; if( !childWithAP->hasStream() ) { continue; } // first lets obtain and decode stream of Annotation appearrence stream std::string & content = childWithAP->getObjectContent(); Filter filter(childWithAP); std::string decodedStream; filter.getDecodedStream(decodedStream); // lets iterate over stream and find operator f and remove it! size_t beg = 0; size_t found = 0; std::string token; while(Parser::getNextWord(token,decodedStream,beg,&found)) { if( token == "f" || token == "F" ) { if((int) found != -1 ) { decodedStream[found] = ' '; } break; } } // Then we need to update Filter section (if any) std::string filterStr = "/Filter"; size_t startOfFlate = Parser::findTokenName(content,filterStr); if((int) startOfFlate != -1 ) { size_t endOfFlate = Parser::findEndOfElementContent(content,startOfFlate+filterStr.size()); childWithAP->eraseContent(startOfFlate,endOfFlate-startOfFlate); //encode and put new stream to object content childWithAP->insertToContent(startOfFlate,"/Filter /FlateDecode "); FlateDecode flate; flate.encode(decodedStream); } // update the length field std::string lengthStr = "/Length"; size_t startOfLength = Parser::findTokenName(content,lengthStr,0); if((int) startOfLength != -1 ) { size_t endOfLength = Parser::findEndOfElementContent(content,startOfLength + lengthStr.size()); childWithAP->eraseContent(startOfLength,endOfLength-startOfLength); std::stringstream ostr; ostr<<"/Length "<< decodedStream.size()<<"\n"; childWithAP->insertToContent(startOfLength,ostr.str()); // update the stream of object with new content std::string stream("stream"); size_t leftBoundOfContentStream = content.find(stream); if((int) leftBoundOfContentStream != -1 ) { size_t rightBoundOfContentStream = content.find("endstream", leftBoundOfContentStream); if((int) rightBoundOfContentStream == -1 ) { rightBoundOfContentStream = content.size() - 1; } childWithAP->eraseContent(leftBoundOfContentStream, rightBoundOfContentStream - leftBoundOfContentStream); decodedStream.insert(0,"\nstream\n"); childWithAP->insertToContent(leftBoundOfContentStream,decodedStream); childWithAP->appendContent("endstream\n"); childWithAP->forgetStreamInFile(); } } } } // sometimes page object does not have resources, // they are inherited from parent object // this method processes such cases and insert resources from parent to page // for correct X-Object transformation static void processBasePageResources(Object *basePage) { if( basePage == NULL ) { return; } std::string resourceToken = "/Resources"; if((int) Parser::findTokenName(basePage->getObjectContent(),resourceToken) == -1 ) { // it seems base page does not have resources, they can be located in parent! Object *resource = basePage->findPatternInObjOrParents(resourceToken); if( resource ) { std::string &resContStr = resource->getObjectContent(); size_t startOfRes = Parser::findTokenName(resContStr,resourceToken); if((int) startOfRes == -1 ) { // no resources at all return; } size_t endOfRes = Parser::findEndOfElementContent(resContStr,startOfRes + resourceToken.size()); if((int) endOfRes == -1 ) { return; // broken resources } std::string resourceContent = resContStr.substr(startOfRes,endOfRes-startOfRes); size_t positionToInsert = basePage->getObjectContent().find("<<"); if((int) positionToInsert == -1 ) { positionToInsert = 0; resourceContent.insert(0,"<<"); resourceContent.append(">>"); } else { positionToInsert += strlen("<<"); } // insert obtained resources to base page basePage->insertToContent(positionToInsert,resourceContent); // if resource contains childs, then we need to add reference to them to current object std::vector resChilds = resource->getChildrenByBounds(startOfRes, endOfRes); std::vector::const_iterator objectIt(resChilds.begin()); const Object::Children & children = resource->getChildren(); for(; objectIt != resChilds.end(); objectIt++ ) { Object::Children::const_iterator childrenIt = children.find( (*objectIt)->getObjectNumber()); if( childrenIt != children.end() ) { Object::ReferencePositionsInContent refPositionInCont = (*childrenIt).second.second; Object::ReferencePositionsInContent::iterator positionIt(refPositionInCont.begin()); Object::ReferencePositionsInContent newPositions; for( ;positionIt != refPositionInCont.end(); positionIt++ ) { newPositions.push_back( (*positionIt) - startOfRes + positionToInsert ); } basePage->addChild( (*objectIt),newPositions ); } } } } } std::string Page::_getMergedPageContent( unsigned int & contentPosition, unsigned int & parentPosition, unsigned int & originalPage1Position, unsigned int & originalPage2Position, std::pair originalPageNumbers, const MergePageDescription & description, Object * basePage, const std::vector & annots, std::vector & annotsPositions ) { std::string content("<<\n/Type /Page\n"); content.append("/Contents "); contentPosition = content.size(); //object number 1 will be recalculated during serialization content.append("1 0 R\n"); Rectangle mediaBox("/MediaBox"); mediaBox.x2 = description.outPageWidth; mediaBox.y2 = description.outPageHeight; mediaBox.appendRectangleToString(content, " "); content.append("/Parent "); parentPosition = content.size(); //object number 1 will be recalculated during serialization content.append("1 0 R\n"); content.append("/Resources <<\n" "/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]\n" "/XObject <<\n"); if(!description.skipOverlayPage) { content.append("/OriginalPage1 "); originalPage1Position = content.size(); content.append(Utils::uIntToStr(originalPageNumbers.first)); content.append(" 0 R\n"); } if(!description.skipBasePage) { content.append("/OriginalPage2 "); originalPage2Position = content.size(); content.append(Utils::uIntToStr(originalPageNumbers.second)); content.append(" "); content.append(Utils::uIntToStr(basePage->getgenerationNumber())); content.append(" R\n"); } content.append(">>\n>>\n"); content.append("/Annots [ "); if(!description.skipBasePage) { Rectangle basePageRectangle("/BBox", basePage->getObjectContent()); for(size_t i = 0; i < annots.size(); ++i) { _updateAnnotFormColor(annots[i]); _recalculateAnnotsCoordinates(annots[i], basePageRectangle, mediaBox, description); Object::ReferencePositionsInContent annotationPosition; annotationPosition.push_back(content.size()); Object::ChildAndItPositionInContent annotAndItPosition(annots[i], annotationPosition); annotsPositions.push_back(annotAndItPosition); content.append(Utils::uIntToStr(annots[i]->getObjectNumber())); content.append(" "); content.append(Utils::uIntToStr(annots[i]->getgenerationNumber())); content.append(" R "); } } content.append("] \n>>\n"); return content; } void Page::merge(Page * sourcePage, Document * parentDocument, MergePageDescription & description, bool isPageDuplicated) { if( sourcePage == NULL ) { description.skipBasePage = true; } if(!description.skipOverlayPage) { // Lets recalculate final transformation of overlay page // before it will be places into XObject Rectangle mediaBox("/MediaBox",_root->getObjectContent()); description.overlayPageTransformation.recalculateTranslation(mediaBox.getWidth(),mediaBox.getHeight()); std::vector fake; _pageToXObject(_root, fake); } std::vector toAllObjects; std::vector annotations; Object * sourcePageToXObject = 0; if(!description.skipBasePage) { RotationHandler rotationHandler(sourcePage->_root, "/Rotate", *this); rotationHandler.processObjectContent(); description.basePageTransformation.addRotation(_rotation); if((int) sourcePage->_root->getObjectContent().find("/Annots") != -1 ) { Object *crop = sourcePage->_root->findPatternInObjOrParents("/CropBox"); if( crop ) { // we need to calculate special compensational shifting // for annotations if cropbox is starting not from 0,0 Rectangle mediaBox("/CropBox", crop->getObjectContent()); if( !Utils::doubleEquals(mediaBox.x1,0) || !Utils::doubleEquals(mediaBox.y1,0) ) { double shiftX = Utils::doubleEquals(mediaBox.x1,0)?0:-mediaBox.x1; double shiftY = Utils::doubleEquals(mediaBox.y1,0)?0:-mediaBox.y1; Translation compensation(shiftX,shiftY); description.basePageTransformation.addAnnotsTransformation(compensation); } } } processBasePageResources(sourcePage->_root); sourcePageToXObject = sourcePage->pageToXObject(toAllObjects, annotations, isPageDuplicated); Rectangle mediaBox("/BBox", sourcePageToXObject->getObjectContent()); description.basePageTransformation.recalculateTranslation(mediaBox.getWidth(),mediaBox.getHeight()); } Object * catalog = 0; unsigned int fake; if(!parentDocument->getDocumentObject()->findObject(std::string("/Kids"), catalog, fake)) { std::string error("Wrong document "); error.append("There is no object with Kids field"); throw Exception(error); } Object::ReferencePositionsInContent pagePosition = catalog->removeChild(_root); //create merged Page unsigned int contentPosition, parentPosition, originalPage1Position, originalPage2Position; std::pair originalPageNumbers(_root->getObjectNumber(), 0); if(!description.skipBasePage) originalPageNumbers.second = sourcePageToXObject->getObjectNumber(); std::vector annotsAndItPositions; std::string mergedPageContent = _getMergedPageContent(contentPosition, parentPosition, originalPage1Position, originalPage2Position, originalPageNumbers, description, sourcePageToXObject, annotations, annotsAndItPositions ); Object * mergedPage = new Object(_root->getObjectNumber(), _root->getgenerationNumber(), mergedPageContent); toAllObjects.push_back(mergedPage); std::vector < unsigned int > contentPositionVec, parentPositionVec, originalPage1PositionVec, originalPage2PositionVec; contentPositionVec.push_back(contentPosition); parentPositionVec.push_back(parentPosition); originalPage1PositionVec.push_back(originalPage1Position); originalPage2PositionVec.push_back(originalPage2Position); Object * contentOfMergedPage = new Object(1, 0, _getContentOfContentObject(description)); toAllObjects.push_back(contentOfMergedPage); parentDocument->addToAllObjects(toAllObjects); mergedPage->addChild(contentOfMergedPage, contentPositionVec); mergedPage->addChild(catalog, parentPositionVec); if(!description.skipOverlayPage) mergedPage->addChild(_root, originalPage1PositionVec); if(!description.skipBasePage) mergedPage->addChild(sourcePageToXObject, originalPage2PositionVec); // Annotation parent page should be changed, since we moved old page // to Xobject if( !description.skipBasePage ) { for(size_t i = 0; i< annotations.size();i++) { _updateAnnotParentPage(annotations[i],mergedPage); } } for(size_t i = 0; i < annotsAndItPositions.size(); ++i) { mergedPage->addChild(annotsAndItPositions[i].first, annotsAndItPositions[i].second); } catalog->addChild(mergedPage, pagePosition); _root = mergedPage; }