/*
* This program 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 3 of the License , or
* ( at your option ) any later version .
*
* This program 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 this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# 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 <iostream>
# include <string.h>
# include "Parser.h"
# include "core/memcheck.h"
using namespace merge_lib ;
Page : : Page ( unsigned int pageNumber ) : _pageNumber ( pageNumber ) , _root ( NULL ) , _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 < Object * > & allObjects , std : : vector < Object * > & annots , bool isCloneNeeded )
{
Object * xObject = ( isCloneNeeded ) ? _root - > getClone ( allObjects ) : _root ;
return _pageToXObject ( xObject , annots ) ;
}
Object * Page : : _pageToXObject ( Object * & page , std : : vector < Object * > & 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 >> \n stream \n " ) ;
content . append ( stream ) ;
content . append ( " endstream \n " ) ;
return content ;
}
void _recalculateAnnotsCoordinates ( Object * annotation ,
const Rectangle & basePagesRectangle ,
const Rectangle & outputPagesRectangle ,
const MergePageDescription & description )
{
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 ( startOfP = = - 1 )
{
return ;
}
size_t endOfP = Parser : : findEndOfElementContent ( annotContent , startOfP + strP . size ( ) ) ;
// lets find object with reference to parent
std : : vector < Object * > 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 " < < newParentPage - > getObjectNumber ( ) < < " " < < newParentPage - > getgenerationNumber ( ) < < " R \n " ;
// to compensate posible deviation
for ( size_t i = strout . str ( ) . size ( ) ; i < endOfP - startOfP ; i + + )
{
strout < < " " ;
}
annotation - > insertToContent ( 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 ( objectContent . find ( " /Widget " ) = = - 1 )
{
return ;
}
size_t startOfAP = Parser : : findTokenName ( objectContent , " /AP " ) ;
if ( startOfAP = = - 1 )
{
return ;
}
size_t endOfAP = objectContent . find ( " >> " , startOfAP ) ;
std : : vector < Object * > 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 ( 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 ( 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 ( 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 ( leftBoundOfContentStream ! = - 1 )
{
size_t rightBoundOfContentStream = content . find ( " endstream " , leftBoundOfContentStream ) ;
if ( rightBoundOfContentStream = = - 1 )
{
rightBoundOfContentStream = content . size ( ) - 1 ;
}
childWithAP - > eraseContent ( leftBoundOfContentStream , rightBoundOfContentStream - leftBoundOfContentStream ) ;
decodedStream . insert ( 0 , " \n stream \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 ( 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 ( startOfRes = = - 1 )
{
// no resources at all
return ;
}
size_t endOfRes = Parser : : findEndOfElementContent ( resContStr , startOfRes + resourceToken . size ( ) ) ;
if ( endOfRes = = - 1 )
{
return ; // broken resources
}
std : : string resourceContent = resContStr . substr ( startOfRes , endOfRes - startOfRes ) ;
size_t positionToInsert = basePage - > getObjectContent ( ) . find ( " << " ) ;
if ( 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 < Object * > resChilds = resource - > getChildrenByBounds ( startOfRes , endOfRes ) ;
std : : vector < Object * > : : 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 < unsigned int , unsigned int > originalPageNumbers ,
const MergePageDescription & description ,
Object * basePage ,
const std : : vector < Object * > & annots ,
std : : vector < Object : : ChildAndItPositionInContent > & 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 < Object * > fake ;
_pageToXObject ( _root , fake ) ;
}
std : : vector < Object * > toAllObjects ;
std : : vector < Object * > annotations ;
Object * sourcePageToXObject = 0 ;
if ( ! description . skipBasePage )
{
RotationHandler rotationHandler ( sourcePage - > _root , " /Rotate " , * this ) ;
rotationHandler . processObjectContent ( ) ;
description . basePageTransformation . addRotation ( _rotation ) ;
if ( 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 < unsigned int , unsigned int > originalPageNumbers ( _root - > getObjectNumber ( ) , 0 ) ;
if ( ! description . skipBasePage )
originalPageNumbers . second = sourcePageToXObject - > getObjectNumber ( ) ;
std : : vector < Object : : ChildAndItPositionInContent > 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 ;
}