mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 22:03:51 -06:00
5316 lines
194 KiB
C++
5316 lines
194 KiB
C++
/******************************************************************************
|
|
* $Id: pdfdataset.cpp 28978 2015-04-23 09:14:09Z rouault $
|
|
*
|
|
* Project: PDF driver
|
|
* Purpose: GDALDataset driver for PDF dataset.
|
|
* Author: Even Rouault, <even dot rouault at mines dash paris dot org>
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2010-2014, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "gdal_pdf.h"
|
|
|
|
#include "cpl_vsi_virtual.h"
|
|
#include "cpl_string.h"
|
|
#include "ogr_spatialref.h"
|
|
#include "ogr_geometry.h"
|
|
#include "cpl_spawn.h"
|
|
|
|
#ifdef HAVE_POPPLER
|
|
#include "cpl_multiproc.h"
|
|
#include "pdfio.h"
|
|
#include <goo/GooList.h>
|
|
#endif // HAVE_POPPLER
|
|
|
|
#include "pdfcreatecopy.h"
|
|
#include <set>
|
|
|
|
#define GDAL_DEFAULT_DPI 150.0
|
|
|
|
/* g++ -fPIC -g -Wall frmts/pdf/pdfdataset.cpp -shared -o gdal_PDF.so -Iport -Igcore -Iogr -L. -lgdal -lpoppler -I/usr/include/poppler */
|
|
|
|
CPL_CVSID("$Id: pdfdataset.cpp 28978 2015-04-23 09:14:09Z rouault $");
|
|
|
|
CPL_C_START
|
|
void GDALRegister_PDF(void);
|
|
CPL_C_END
|
|
|
|
#if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
|
|
static const char* pszOpenOptionList =
|
|
"<OpenOptionList>"
|
|
" <Option name='RENDERING_OPTIONS' type='string-select' description='Which graphical elements to render' default='RASTER,VECTOR,TEXT' alt_config_option='GDAL_PDF_RENDERING_OPTIONS'>"
|
|
" <Value>RASTER,VECTOR,TEXT</Value>\n"
|
|
" <Value>RASTER,VECTOR</Value>\n"
|
|
" <Value>RASTER,TEXT</Value>\n"
|
|
" <Value>RASTER</Value>\n"
|
|
" <Value>VECTOR,TEXT</Value>\n"
|
|
" <Value>VECTOR</Value>\n"
|
|
" <Value>TEXT</Value>\n"
|
|
" </Option>"
|
|
" <Option name='DPI' type='float' description='Resolution in Dot Per Inch' default='72' alt_config_option='GDAL_PDF_DPI'/>"
|
|
" <Option name='USER_PWD' type='string' description='Password' alt_config_option='PDF_USER_PWD'/>"
|
|
#if defined(HAVE_POPPLER) && defined(HAVE_PODOFO)
|
|
" <Option name='PDF_LIB' type='string-select' description='Which underlying PDF library to use' default='POPPLER' alt_config_option='GDAL_PDF_LIB'>"
|
|
" <Value>POPPLER</Value>\n"
|
|
" <Value>PODOFO</Value>\n"
|
|
" </Option>"
|
|
#endif // HAVE_POPPLER && HAVE_PODOFO
|
|
" <Option name='LAYERS' type='string' description='List of layers (comma separated) to turn ON (or ALL to turn all layers ON)' alt_config_option='GDAL_PDF_LAYERS'/>"
|
|
" <Option name='LAYERS_OFF' type='string' description='List of layers (comma separated) to turn OFF' alt_config_option='GDAL_PDF_LAYERS_OFF'/>"
|
|
" <Option name='BANDS' type='string-select' description='Number of raster bands' default='3' alt_config_option='GDAL_PDF_BANDS'>"
|
|
" <Value>3</Value>\n"
|
|
" <Value>4</Value>\n"
|
|
" </Option>"
|
|
" <Option name='NEATLINE' type='string' description='The name of the neatline to select' alt_config_option='GDAL_PDF_NEATLINE'/>"
|
|
"</OpenOptionList>";
|
|
|
|
static double Get(GDALPDFObject* poObj, int nIndice = -1);
|
|
|
|
#ifdef HAVE_POPPLER
|
|
|
|
static CPLMutex* hGlobalParamsMutex = NULL;
|
|
|
|
/************************************************************************/
|
|
/* ObjectAutoFree */
|
|
/************************************************************************/
|
|
|
|
class ObjectAutoFree : public Object
|
|
{
|
|
public:
|
|
ObjectAutoFree() {}
|
|
~ObjectAutoFree() { free(); }
|
|
};
|
|
|
|
|
|
/************************************************************************/
|
|
/* GDALPDFOutputDev */
|
|
/************************************************************************/
|
|
|
|
class GDALPDFOutputDev : public SplashOutputDev
|
|
{
|
|
private:
|
|
int bEnableVector;
|
|
int bEnableText;
|
|
int bEnableBitmap;
|
|
|
|
void skipBytes(Stream *str,
|
|
int width, int height,
|
|
int nComps, int nBits)
|
|
{
|
|
int nVals = width * nComps;
|
|
int nLineSize = (nVals * nBits + 7) >> 3;
|
|
int nBytes = nLineSize * height;
|
|
for (int i = 0; i < nBytes; i++)
|
|
{
|
|
if( str->getChar() == EOF)
|
|
break;
|
|
}
|
|
}
|
|
|
|
public:
|
|
GDALPDFOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
|
|
GBool reverseVideoA, SplashColorPtr paperColorA) :
|
|
SplashOutputDev(colorModeA, bitmapRowPadA,
|
|
reverseVideoA, paperColorA),
|
|
bEnableVector(TRUE),
|
|
bEnableText(TRUE),
|
|
bEnableBitmap(TRUE) {}
|
|
|
|
void SetEnableVector(int bFlag) { bEnableVector = bFlag; }
|
|
void SetEnableText(int bFlag) { bEnableText = bFlag; }
|
|
void SetEnableBitmap(int bFlag) { bEnableBitmap = bFlag; }
|
|
|
|
virtual void startPage(int pageNum, GfxState *state
|
|
#ifdef POPPLER_0_23_OR_LATER
|
|
,XRef* xref
|
|
#endif
|
|
)
|
|
{
|
|
SplashOutputDev::startPage(pageNum, state
|
|
#ifdef POPPLER_0_23_OR_LATER
|
|
,xref
|
|
#endif
|
|
);
|
|
SplashBitmap* poBitmap = getBitmap();
|
|
memset(poBitmap->getDataPtr(), 255, poBitmap->getRowSize() * poBitmap->getHeight());
|
|
}
|
|
|
|
virtual void stroke(GfxState * state)
|
|
{
|
|
if (bEnableVector)
|
|
SplashOutputDev::stroke(state);
|
|
}
|
|
|
|
virtual void fill(GfxState * state)
|
|
{
|
|
if (bEnableVector)
|
|
SplashOutputDev::fill(state);
|
|
}
|
|
|
|
virtual void eoFill(GfxState * state)
|
|
{
|
|
if (bEnableVector)
|
|
SplashOutputDev::eoFill(state);
|
|
}
|
|
|
|
virtual void drawChar(GfxState *state, double x, double y,
|
|
double dx, double dy,
|
|
double originX, double originY,
|
|
CharCode code, int nBytes, Unicode *u, int uLen)
|
|
{
|
|
if (bEnableText)
|
|
SplashOutputDev::drawChar(state, x, y, dx, dy,
|
|
originX, originY,
|
|
code, nBytes, u, uLen);
|
|
}
|
|
|
|
virtual void beginTextObject(GfxState *state)
|
|
{
|
|
if (bEnableText)
|
|
SplashOutputDev::beginTextObject(state);
|
|
}
|
|
|
|
#ifndef POPPLER_0_23_OR_LATER
|
|
virtual GBool deviceHasTextClip(GfxState *state)
|
|
{
|
|
if (bEnableText)
|
|
return SplashOutputDev::deviceHasTextClip(state);
|
|
return gFalse;
|
|
}
|
|
#endif
|
|
|
|
virtual void endTextObject(GfxState *state)
|
|
{
|
|
if (bEnableText)
|
|
SplashOutputDev::endTextObject(state);
|
|
}
|
|
|
|
virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
|
|
int width, int height, GBool invert,
|
|
GBool interpolate, GBool inlineImg)
|
|
{
|
|
if (bEnableBitmap)
|
|
SplashOutputDev::drawImageMask(state, ref, str,
|
|
width, height, invert,
|
|
interpolate, inlineImg);
|
|
else
|
|
{
|
|
str->reset();
|
|
if (inlineImg)
|
|
{
|
|
skipBytes(str, width, height, 1, 1);
|
|
}
|
|
str->close();
|
|
}
|
|
}
|
|
|
|
#ifdef POPPLER_0_20_OR_LATER
|
|
virtual void setSoftMaskFromImageMask(GfxState *state,
|
|
Object *ref, Stream *str,
|
|
int width, int height, GBool invert,
|
|
GBool inlineImg, double *baseMatrix)
|
|
{
|
|
if (bEnableBitmap)
|
|
SplashOutputDev::setSoftMaskFromImageMask(state, ref, str,
|
|
width, height, invert,
|
|
inlineImg, baseMatrix);
|
|
else
|
|
str->close();
|
|
}
|
|
|
|
virtual void unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix)
|
|
{
|
|
if (bEnableBitmap)
|
|
SplashOutputDev::unsetSoftMaskFromImageMask(state, baseMatrix);
|
|
}
|
|
#endif
|
|
|
|
virtual void drawImage(GfxState *state, Object *ref, Stream *str,
|
|
int width, int height, GfxImageColorMap *colorMap,
|
|
GBool interpolate, int *maskColors, GBool inlineImg)
|
|
{
|
|
if (bEnableBitmap)
|
|
SplashOutputDev::drawImage(state, ref, str,
|
|
width, height, colorMap,
|
|
interpolate, maskColors, inlineImg);
|
|
else
|
|
{
|
|
str->reset();
|
|
if (inlineImg)
|
|
{
|
|
skipBytes(str, width, height,
|
|
colorMap->getNumPixelComps(),
|
|
colorMap->getBits());
|
|
}
|
|
str->close();
|
|
}
|
|
}
|
|
|
|
virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
|
|
int width, int height,
|
|
GfxImageColorMap *colorMap,
|
|
GBool interpolate,
|
|
Stream *maskStr, int maskWidth, int maskHeight,
|
|
GBool maskInvert, GBool maskInterpolate)
|
|
{
|
|
if (bEnableBitmap)
|
|
SplashOutputDev::drawMaskedImage(state, ref, str,
|
|
width, height, colorMap,
|
|
interpolate,
|
|
maskStr, maskWidth, maskHeight,
|
|
maskInvert, maskInterpolate);
|
|
else
|
|
str->close();
|
|
}
|
|
|
|
virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
|
|
int width, int height,
|
|
GfxImageColorMap *colorMap,
|
|
GBool interpolate,
|
|
Stream *maskStr,
|
|
int maskWidth, int maskHeight,
|
|
GfxImageColorMap *maskColorMap,
|
|
GBool maskInterpolate)
|
|
{
|
|
if (bEnableBitmap)
|
|
{
|
|
if( maskColorMap->getBits() <= 0 ) /* workaround poppler bug (robustness) */
|
|
{
|
|
str->close();
|
|
return;
|
|
}
|
|
SplashOutputDev::drawSoftMaskedImage(state, ref, str,
|
|
width, height, colorMap,
|
|
interpolate,
|
|
maskStr, maskWidth, maskHeight,
|
|
maskColorMap, maskInterpolate);
|
|
}
|
|
else
|
|
str->close();
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* Dump routines */
|
|
/************************************************************************/
|
|
|
|
class GDALPDFDumper
|
|
{
|
|
private:
|
|
FILE* f;
|
|
int nDepthLimit;
|
|
std::set< int > aoSetObjectExplored;
|
|
int bDumpParent;
|
|
|
|
void DumpSimplified(GDALPDFObject* poObj);
|
|
|
|
public:
|
|
GDALPDFDumper(FILE* fIn, int nDepthLimitIn = -1) : f(fIn), nDepthLimit(nDepthLimitIn)
|
|
{
|
|
bDumpParent = CSLTestBoolean(CPLGetConfigOption("PDF_DUMP_PARENT", "FALSE"));
|
|
}
|
|
|
|
void Dump(GDALPDFObject* poObj, int nDepth = 0);
|
|
void Dump(GDALPDFDictionary* poDict, int nDepth = 0);
|
|
void Dump(GDALPDFArray* poArray, int nDepth = 0);
|
|
};
|
|
|
|
void GDALPDFDumper::Dump(GDALPDFArray* poArray, int nDepth)
|
|
{
|
|
if (nDepthLimit >= 0 && nDepth > nDepthLimit)
|
|
return;
|
|
|
|
int nLength = poArray->GetLength();
|
|
int i;
|
|
CPLString osIndent;
|
|
for(i=0;i<nDepth;i++)
|
|
osIndent += " ";
|
|
for(i=0;i<nLength;i++)
|
|
{
|
|
fprintf(f, "%sItem[%d]:", osIndent.c_str(), i);
|
|
GDALPDFObject* poObj = NULL;
|
|
if ((poObj = poArray->Get(i)) != NULL)
|
|
{
|
|
if (poObj->GetType() == PDFObjectType_String ||
|
|
poObj->GetType() == PDFObjectType_Null ||
|
|
poObj->GetType() == PDFObjectType_Bool ||
|
|
poObj->GetType() == PDFObjectType_Int ||
|
|
poObj->GetType() == PDFObjectType_Real ||
|
|
poObj->GetType() == PDFObjectType_Name)
|
|
{
|
|
fprintf(f, " ");
|
|
DumpSimplified(poObj);
|
|
fprintf(f, "\n");
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
Dump( poObj, nDepth+1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GDALPDFDumper::DumpSimplified(GDALPDFObject* poObj)
|
|
{
|
|
switch(poObj->GetType())
|
|
{
|
|
case PDFObjectType_String:
|
|
fprintf(f, "%s (string)", poObj->GetString().c_str());
|
|
break;
|
|
|
|
case PDFObjectType_Null:
|
|
fprintf(f, "null");
|
|
break;
|
|
|
|
case PDFObjectType_Bool:
|
|
fprintf(f, "%s (bool)", poObj->GetBool() ? "true" : "false");
|
|
break;
|
|
|
|
case PDFObjectType_Int:
|
|
fprintf(f, "%d (int)", poObj->GetInt());
|
|
break;
|
|
|
|
case PDFObjectType_Real:
|
|
fprintf(f, "%f (real)", poObj->GetReal());
|
|
break;
|
|
|
|
case PDFObjectType_Name:
|
|
fprintf(f, "%s (name)", poObj->GetName().c_str());
|
|
break;
|
|
|
|
default:
|
|
fprintf(f, "unknown !");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GDALPDFDumper::Dump(GDALPDFObject* poObj, int nDepth)
|
|
{
|
|
if (nDepthLimit >= 0 && nDepth > nDepthLimit)
|
|
return;
|
|
|
|
int i;
|
|
CPLString osIndent;
|
|
for(i=0;i<nDepth;i++)
|
|
osIndent += " ";
|
|
fprintf(f, "%sType = %s",
|
|
osIndent.c_str(), poObj->GetTypeName());
|
|
int nRefNum = poObj->GetRefNum();
|
|
if (nRefNum != 0)
|
|
fprintf(f, ", Num = %d, Gen = %d",
|
|
nRefNum, poObj->GetRefGen());
|
|
fprintf(f, "\n");
|
|
|
|
if (nRefNum != 0)
|
|
{
|
|
if (aoSetObjectExplored.find(nRefNum) != aoSetObjectExplored.end())
|
|
return;
|
|
aoSetObjectExplored.insert(nRefNum);
|
|
}
|
|
|
|
switch(poObj->GetType())
|
|
{
|
|
case PDFObjectType_Array:
|
|
Dump(poObj->GetArray(), nDepth+1);
|
|
break;
|
|
|
|
case PDFObjectType_Dictionary:
|
|
Dump(poObj->GetDictionary(), nDepth+1);
|
|
break;
|
|
|
|
case PDFObjectType_String:
|
|
case PDFObjectType_Null:
|
|
case PDFObjectType_Bool:
|
|
case PDFObjectType_Int:
|
|
case PDFObjectType_Real:
|
|
case PDFObjectType_Name:
|
|
fprintf(f, "%s", osIndent.c_str());
|
|
DumpSimplified(poObj);
|
|
fprintf(f, "\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(f, "%s", osIndent.c_str());
|
|
fprintf(f, "unknown !\n");
|
|
break;
|
|
}
|
|
|
|
GDALPDFStream* poStream = poObj->GetStream();
|
|
if (poStream != NULL)
|
|
{
|
|
fprintf(f, "%sHas stream (%d bytes)\n", osIndent.c_str(), poStream->GetLength());
|
|
}
|
|
}
|
|
|
|
void GDALPDFDumper::Dump(GDALPDFDictionary* poDict, int nDepth)
|
|
{
|
|
if (nDepthLimit >= 0 && nDepth > nDepthLimit)
|
|
return;
|
|
|
|
std::map<CPLString, GDALPDFObject*>& oMap = poDict->GetValues();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oIter = oMap.begin();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oEnd = oMap.end();
|
|
int i;
|
|
CPLString osIndent;
|
|
for(i=0;i<nDepth;i++)
|
|
osIndent += " ";
|
|
for(i=0;oIter != oEnd;++oIter, i++)
|
|
{
|
|
const char* pszKey = oIter->first.c_str();
|
|
fprintf(f, "%sItem[%d] : %s", osIndent.c_str(), i, pszKey);
|
|
GDALPDFObject* poObj = oIter->second;
|
|
if (strcmp(pszKey, "Parent") == 0 && !bDumpParent)
|
|
{
|
|
if (poObj->GetRefNum())
|
|
fprintf(f, ", Num = %d, Gen = %d",
|
|
poObj->GetRefNum(), poObj->GetRefGen());
|
|
fprintf(f, "\n");
|
|
continue;
|
|
}
|
|
if (poObj != NULL)
|
|
{
|
|
if (poObj->GetType() == PDFObjectType_String ||
|
|
poObj->GetType() == PDFObjectType_Null ||
|
|
poObj->GetType() == PDFObjectType_Bool ||
|
|
poObj->GetType() == PDFObjectType_Int ||
|
|
poObj->GetType() == PDFObjectType_Real ||
|
|
poObj->GetType() == PDFObjectType_Name)
|
|
{
|
|
fprintf(f, " = ");
|
|
DumpSimplified(poObj);
|
|
fprintf(f, "\n");
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
Dump(poObj, nDepth+1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PDFRasterBand() */
|
|
/************************************************************************/
|
|
|
|
PDFRasterBand::PDFRasterBand( PDFDataset *poDS, int nBand )
|
|
|
|
{
|
|
this->poDS = poDS;
|
|
this->nBand = nBand;
|
|
|
|
eDataType = GDT_Byte;
|
|
|
|
if( poDS->nBlockXSize )
|
|
{
|
|
nBlockXSize = poDS->nBlockXSize;
|
|
nBlockYSize = poDS->nBlockYSize;
|
|
}
|
|
else if( poDS->GetRasterXSize() < 64 * 1024 * 1024 / poDS->GetRasterYSize() )
|
|
{
|
|
nBlockXSize = poDS->GetRasterXSize();
|
|
nBlockYSize = 1;
|
|
}
|
|
else
|
|
{
|
|
nBlockXSize = MIN(1024, poDS->GetRasterXSize());
|
|
nBlockYSize = MIN(1024, poDS->GetRasterYSize());
|
|
poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetColorInterpretation() */
|
|
/************************************************************************/
|
|
|
|
GDALColorInterp PDFRasterBand::GetColorInterpretation()
|
|
{
|
|
PDFDataset *poGDS = (PDFDataset *) poDS;
|
|
if (poGDS->nBands == 1)
|
|
return GCI_GrayIndex;
|
|
else
|
|
return (GDALColorInterp)(GCI_RedBand + (nBand - 1));
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlockFromTile() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFRasterBand::IReadBlockFromTile( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
|
|
{
|
|
PDFDataset *poGDS = (PDFDataset *) poDS;
|
|
|
|
int nReqXSize = nBlockXSize;
|
|
int nReqYSize = nBlockYSize;
|
|
if( (nBlockXOff + 1) * nBlockXSize > nRasterXSize )
|
|
nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
|
|
if( (nBlockYOff + 1) * nBlockYSize > nRasterYSize )
|
|
nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
|
|
|
|
int nXBlocks = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
|
|
int iTile = poGDS->aiTiles[nBlockYOff * nXBlocks + nBlockXOff];
|
|
GDALPDFTileDesc& sTile = poGDS->asTiles[iTile];
|
|
GDALPDFObject* poImage = sTile.poImage;
|
|
|
|
if( iTile < 0 )
|
|
{
|
|
memset(pImage, 0, nBlockXSize * nBlockYSize);
|
|
return CE_None;
|
|
}
|
|
|
|
if( nBand == 4 )
|
|
{
|
|
GDALPDFDictionary* poImageDict = poImage->GetDictionary();
|
|
GDALPDFObject* poSMask = poImageDict->Get("SMask");
|
|
if( poSMask != NULL && poSMask->GetType() == PDFObjectType_Dictionary )
|
|
{
|
|
GDALPDFDictionary* poSMaskDict = poSMask->GetDictionary();
|
|
GDALPDFObject* poWidth = poSMaskDict->Get("Width");
|
|
GDALPDFObject* poHeight = poSMaskDict->Get("Height");
|
|
GDALPDFObject* poColorSpace = poSMaskDict->Get("ColorSpace");
|
|
GDALPDFObject* poBitsPerComponent = poSMaskDict->Get("BitsPerComponent");
|
|
int nBits = 0;
|
|
if( poBitsPerComponent )
|
|
nBits = (int)Get(poBitsPerComponent);
|
|
if (poWidth && Get(poWidth) == nReqXSize &&
|
|
poHeight && Get(poHeight) == nReqYSize &&
|
|
poColorSpace && poColorSpace->GetType() == PDFObjectType_Name &&
|
|
poColorSpace->GetName() == "DeviceGray" &&
|
|
(nBits == 1 || nBits == 8) )
|
|
{
|
|
GDALPDFStream* poStream = poSMask->GetStream();
|
|
GByte* pabyStream = NULL;
|
|
|
|
if( poStream == NULL )
|
|
return CE_Failure;
|
|
|
|
pabyStream = (GByte*) poStream->GetBytes();
|
|
if( pabyStream == NULL )
|
|
return CE_Failure;
|
|
|
|
int nReqXSize1 = (nReqXSize + 7) / 8;
|
|
if( (nBits == 8 && poStream->GetLength() != nReqXSize * nReqYSize) ||
|
|
(nBits == 1 && poStream->GetLength() != nReqXSize1 * nReqYSize) )
|
|
{
|
|
VSIFree(pabyStream);
|
|
return CE_Failure;
|
|
}
|
|
|
|
GByte* pabyData = (GByte*) pImage;
|
|
if( nReqXSize != nBlockXSize || nReqYSize != nBlockYSize )
|
|
{
|
|
memset(pabyData, 0, nBlockXSize * nBlockYSize);
|
|
}
|
|
|
|
if( nBits == 8 )
|
|
{
|
|
for(int j = 0; j < nReqYSize; j++)
|
|
{
|
|
for(int i = 0; i < nReqXSize; i++)
|
|
{
|
|
pabyData[j * nBlockXSize + i] = pabyStream[j * nReqXSize + i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int j = 0; j < nReqYSize; j++)
|
|
{
|
|
for(int i = 0; i < nReqXSize; i++)
|
|
{
|
|
if( pabyStream[j * nReqXSize1 + i / 8] & (1 << (7 - (i % 8))) )
|
|
pabyData[j * nBlockXSize + i] = 255;
|
|
else
|
|
pabyData[j * nBlockXSize + i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
VSIFree(pabyStream);
|
|
return CE_None;
|
|
}
|
|
}
|
|
|
|
memset(pImage, 255, nBlockXSize * nBlockYSize);
|
|
return CE_None;
|
|
}
|
|
|
|
if( poGDS->nLastBlockXOff == nBlockXOff &&
|
|
poGDS->nLastBlockYOff == nBlockYOff &&
|
|
poGDS->pabyCachedData != NULL )
|
|
{
|
|
CPLDebug("PDF", "Using cached block (%d, %d)",
|
|
nBlockXOff, nBlockYOff);
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
if (poGDS->bTried == FALSE)
|
|
{
|
|
poGDS->bTried = TRUE;
|
|
poGDS->pabyCachedData = (GByte*)VSIMalloc3(3, nBlockXSize, nBlockYSize);
|
|
}
|
|
if (poGDS->pabyCachedData == NULL)
|
|
return CE_Failure;
|
|
|
|
GDALPDFStream* poStream = poImage->GetStream();
|
|
GByte* pabyStream = NULL;
|
|
|
|
if( poStream == NULL )
|
|
return CE_Failure;
|
|
|
|
pabyStream = (GByte*) poStream->GetBytes();
|
|
if( pabyStream == NULL )
|
|
return CE_Failure;
|
|
|
|
if( poStream->GetLength() != sTile.nBands * nReqXSize * nReqYSize)
|
|
{
|
|
VSIFree(pabyStream);
|
|
return CE_Failure;
|
|
}
|
|
|
|
memcpy(poGDS->pabyCachedData, pabyStream, poStream->GetLength());
|
|
VSIFree(pabyStream);
|
|
poGDS->nLastBlockXOff = nBlockXOff;
|
|
poGDS->nLastBlockYOff = nBlockYOff;
|
|
}
|
|
|
|
GByte* pabyData = (GByte*) pImage;
|
|
if( nBand != 4 && (nReqXSize != nBlockXSize || nReqYSize != nBlockYSize) )
|
|
{
|
|
memset(pabyData, 0, nBlockXSize * nBlockYSize);
|
|
}
|
|
|
|
if( poGDS->nBands >= 3 && sTile.nBands == 3 )
|
|
{
|
|
for(int j = 0; j < nReqYSize; j++)
|
|
{
|
|
for(int i = 0; i < nReqXSize; i++)
|
|
{
|
|
pabyData[j * nBlockXSize + i] = poGDS->pabyCachedData[3 * (j * nReqXSize + i) + nBand - 1];
|
|
}
|
|
}
|
|
}
|
|
else if( sTile.nBands == 1 )
|
|
{
|
|
for(int j = 0; j < nReqYSize; j++)
|
|
{
|
|
for(int i = 0; i < nReqXSize; i++)
|
|
{
|
|
pabyData[j * nBlockXSize + i] = poGDS->pabyCachedData[j * nReqXSize + i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
|
|
{
|
|
PDFDataset *poGDS = (PDFDataset *) poDS;
|
|
|
|
if (poGDS->aiTiles.size() )
|
|
{
|
|
if ( IReadBlockFromTile(nBlockXOff, nBlockYOff,
|
|
pImage) == CE_None )
|
|
{
|
|
return CE_None;
|
|
}
|
|
else
|
|
{
|
|
poGDS->aiTiles.resize(0);
|
|
poGDS->bTried = FALSE;
|
|
CPLFree(poGDS->pabyCachedData);
|
|
poGDS->pabyCachedData = NULL;
|
|
poGDS->nLastBlockXOff = -1;
|
|
poGDS->nLastBlockYOff = -1;
|
|
}
|
|
}
|
|
|
|
int nReqXSize = nBlockXSize;
|
|
int nReqYSize = nBlockYSize;
|
|
if( (nBlockXOff + 1) * nBlockXSize > nRasterXSize )
|
|
nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
|
|
if( nBlockYSize == 1 )
|
|
nReqYSize = nRasterYSize;
|
|
else if( (nBlockYOff + 1) * nBlockYSize > nRasterYSize )
|
|
nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
|
|
|
|
if (poGDS->bTried == FALSE)
|
|
{
|
|
poGDS->bTried = TRUE;
|
|
if( nBlockYSize == 1 )
|
|
poGDS->pabyCachedData = (GByte*)VSIMalloc3(MAX(3, poGDS->nBands), nRasterXSize, nRasterYSize);
|
|
else
|
|
poGDS->pabyCachedData = (GByte*)VSIMalloc3(MAX(3, poGDS->nBands), nBlockXSize, nBlockYSize);
|
|
}
|
|
if (poGDS->pabyCachedData == NULL)
|
|
return CE_Failure;
|
|
|
|
if( poGDS->nLastBlockXOff == nBlockXOff &&
|
|
(nBlockYSize == 1 || poGDS->nLastBlockYOff == nBlockYOff) &&
|
|
poGDS->pabyCachedData != NULL )
|
|
{
|
|
/*CPLDebug("PDF", "Using cached block (%d, %d)",
|
|
nBlockXOff, nBlockYOff);*/
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_PODOFO
|
|
if (!poGDS->bUsePoppler && nBand == 4)
|
|
{
|
|
memset(pImage, 255, nBlockXSize * nBlockYSize);
|
|
return CE_None;
|
|
}
|
|
#endif
|
|
|
|
CPLErr eErr = poGDS->ReadPixels( nBlockXOff * nBlockXSize,
|
|
(nBlockYSize == 1) ? 0 : nBlockYOff * nBlockYSize,
|
|
nReqXSize,
|
|
nReqYSize,
|
|
1,
|
|
nBlockXSize,
|
|
nBlockXSize * ((nBlockYSize == 1) ? nRasterYSize : nBlockYSize),
|
|
poGDS->pabyCachedData);
|
|
if( eErr == CE_None )
|
|
{
|
|
poGDS->nLastBlockXOff = nBlockXOff;
|
|
poGDS->nLastBlockYOff = nBlockYOff;
|
|
}
|
|
else
|
|
{
|
|
CPLFree(poGDS->pabyCachedData);
|
|
poGDS->pabyCachedData = NULL;
|
|
}
|
|
|
|
}
|
|
if (poGDS->pabyCachedData == NULL)
|
|
return CE_Failure;
|
|
|
|
if( nBlockYSize == 1 )
|
|
memcpy(pImage,
|
|
poGDS->pabyCachedData + (nBand - 1) * nBlockXSize * nRasterYSize + nBlockYOff * nBlockXSize,
|
|
nBlockXSize);
|
|
else
|
|
memcpy(pImage,
|
|
poGDS->pabyCachedData + (nBand - 1) * nBlockXSize * nBlockYSize,
|
|
nBlockXSize * nBlockYSize);
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetOption() */
|
|
/************************************************************************/
|
|
|
|
const char* PDFDataset::GetOption(char** papszOpenOptions,
|
|
const char* pszOptionName,
|
|
const char* pszDefaultVal)
|
|
{
|
|
CPLErr eLastErrType = CPLGetLastErrorType();
|
|
int nLastErrno = CPLGetLastErrorNo();
|
|
CPLString osLastErrorMsg(CPLGetLastErrorMsg());
|
|
CPLXMLNode* psNode = CPLParseXMLString(pszOpenOptionList);
|
|
CPLErrorSetState(eLastErrType, nLastErrno, osLastErrorMsg);
|
|
if( psNode == NULL ) return pszDefaultVal;
|
|
CPLXMLNode* psIter = psNode->psChild;
|
|
while( psIter != NULL )
|
|
{
|
|
if( EQUAL(CPLGetXMLValue( psIter, "name", "" ), pszOptionName) )
|
|
{
|
|
const char* pszVal = CSLFetchNameValue(papszOpenOptions, pszOptionName);
|
|
if( pszVal != NULL )
|
|
{
|
|
CPLDestroyXMLNode(psNode);
|
|
return pszVal;
|
|
}
|
|
const char* pszAltConfigOption = CPLGetXMLValue( psIter, "alt_config_option", NULL );
|
|
if( pszAltConfigOption != NULL )
|
|
{
|
|
pszVal = CPLGetConfigOption(pszAltConfigOption, pszDefaultVal);
|
|
CPLDestroyXMLNode(psNode);
|
|
return pszVal;
|
|
}
|
|
CPLDestroyXMLNode(psNode);
|
|
return pszDefaultVal;
|
|
}
|
|
psIter = psIter->psNext;
|
|
}
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Requesting an undocumented open option '%s'", pszOptionName);
|
|
CPLDestroyXMLNode(psNode);
|
|
return pszDefaultVal;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ReadPixels() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::ReadPixels( int nReqXOff, int nReqYOff,
|
|
int nReqXSize, int nReqYSize,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GSpacing nBandSpace,
|
|
GByte* pabyData )
|
|
{
|
|
CPLErr eErr = CE_None;
|
|
const char* pszRenderingOptions = GetOption(papszOpenOptions, "RENDERING_OPTIONS", NULL);
|
|
|
|
#ifdef HAVE_POPPLER
|
|
if(bUsePoppler)
|
|
{
|
|
SplashColor sColor;
|
|
sColor[0] = 255;
|
|
sColor[1] = 255;
|
|
sColor[2] = 255;
|
|
GDALPDFOutputDev *poSplashOut;
|
|
poSplashOut = new GDALPDFOutputDev((nBands < 4) ? splashModeRGB8 : splashModeXBGR8,
|
|
4, gFalse,
|
|
(nBands < 4) ? sColor : NULL);
|
|
|
|
if (pszRenderingOptions != NULL)
|
|
{
|
|
poSplashOut->SetEnableVector(FALSE);
|
|
poSplashOut->SetEnableText(FALSE);
|
|
poSplashOut->SetEnableBitmap(FALSE);
|
|
|
|
char** papszTokens = CSLTokenizeString2( pszRenderingOptions, " ,", 0 );
|
|
for(int i=0;papszTokens[i] != NULL;i++)
|
|
{
|
|
if (EQUAL(papszTokens[i], "VECTOR"))
|
|
poSplashOut->SetEnableVector(TRUE);
|
|
else if (EQUAL(papszTokens[i], "TEXT"))
|
|
poSplashOut->SetEnableText(TRUE);
|
|
else if (EQUAL(papszTokens[i], "RASTER") ||
|
|
EQUAL(papszTokens[i], "BITMAP"))
|
|
poSplashOut->SetEnableBitmap(TRUE);
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Value %s is not a valid value for GDAL_PDF_RENDERING_OPTIONS",
|
|
papszTokens[i]);
|
|
}
|
|
}
|
|
CSLDestroy(papszTokens);
|
|
}
|
|
|
|
PDFDoc* poDoc = poDocPoppler;
|
|
#ifdef POPPLER_0_20_OR_LATER
|
|
poSplashOut->startDoc(poDoc);
|
|
#else
|
|
poSplashOut->startDoc(poDoc->getXRef());
|
|
#endif
|
|
|
|
/* EVIL: we modify a private member... */
|
|
/* poppler (at least 0.12 and 0.14 versions) don't render correctly */
|
|
/* some PDFs and display an error message 'Could not find a OCG with Ref' */
|
|
/* in those cases. This processing of optional content is an addition of */
|
|
/* poppler in comparison to original xpdf, which hasn't the issue. All in */
|
|
/* all, nullifying optContent removes the error message and improves the rendering */
|
|
#ifdef POPPLER_HAS_OPTCONTENT
|
|
Catalog* poCatalog = poDoc->getCatalog();
|
|
OCGs* poOldOCGs = poCatalog->optContent;
|
|
if (!bUseOCG)
|
|
poCatalog->optContent = NULL;
|
|
#endif
|
|
poDoc->displayPageSlice(poSplashOut,
|
|
iPage,
|
|
dfDPI, dfDPI,
|
|
0,
|
|
TRUE, gFalse, gFalse,
|
|
nReqXOff, nReqYOff,
|
|
nReqXSize, nReqYSize);
|
|
|
|
/* Restore back */
|
|
#ifdef POPPLER_HAS_OPTCONTENT
|
|
poCatalog->optContent = poOldOCGs;
|
|
#endif
|
|
|
|
SplashBitmap* poBitmap = poSplashOut->getBitmap();
|
|
if (poBitmap->getWidth() != nReqXSize || poBitmap->getHeight() != nReqYSize)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Bitmap decoded size (%dx%d) doesn't match raster size (%dx%d)" ,
|
|
poBitmap->getWidth(), poBitmap->getHeight(),
|
|
nReqXSize, nReqYSize);
|
|
delete poSplashOut;
|
|
return CE_Failure;
|
|
}
|
|
|
|
GByte* pabyDataR = pabyData;
|
|
GByte* pabyDataG = pabyData + nBandSpace;
|
|
GByte* pabyDataB = pabyData + 2 * nBandSpace;
|
|
GByte* pabyDataA = pabyData + 3 * nBandSpace;
|
|
GByte* pabySrc = poBitmap->getDataPtr();
|
|
GByte* pabyAlphaSrc = (GByte*)poBitmap->getAlphaPtr();
|
|
int i, j;
|
|
for(j=0;j<nReqYSize;j++)
|
|
{
|
|
for(i=0;i<nReqXSize;i++)
|
|
{
|
|
if (nBands < 4)
|
|
{
|
|
pabyDataR[i * nPixelSpace] = pabySrc[i * 3 + 0];
|
|
pabyDataG[i * nPixelSpace] = pabySrc[i * 3 + 1];
|
|
pabyDataB[i * nPixelSpace] = pabySrc[i * 3 + 2];
|
|
}
|
|
else
|
|
{
|
|
pabyDataR[i * nPixelSpace] = pabySrc[i * 4 + 2];
|
|
pabyDataG[i * nPixelSpace] = pabySrc[i * 4 + 1];
|
|
pabyDataB[i * nPixelSpace] = pabySrc[i * 4 + 0];
|
|
pabyDataA[i * nPixelSpace] = pabyAlphaSrc[i];
|
|
}
|
|
}
|
|
pabyDataR += nLineSpace;
|
|
pabyDataG += nLineSpace;
|
|
pabyDataB += nLineSpace;
|
|
pabyDataA += nLineSpace;
|
|
pabyAlphaSrc += poBitmap->getAlphaRowSize();
|
|
pabySrc += poBitmap->getRowSize();
|
|
}
|
|
delete poSplashOut;
|
|
}
|
|
#endif // HAVE_POPPLER
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
{
|
|
if( bPdfToPpmFailed )
|
|
return CE_Failure;
|
|
|
|
if (pszRenderingOptions != NULL)
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"GDAL_PDF_RENDERING_OPTIONS only supported "
|
|
"when PDF driver is compiled against Poppler.");
|
|
}
|
|
|
|
CPLString osTmpFilename;
|
|
int nRet;
|
|
|
|
#ifdef notdef
|
|
int bUseSpawn = CSLTestBoolean(CPLGetConfigOption("GDAL_PDF_USE_SPAWN", "YES"));
|
|
if( !bUseSpawn )
|
|
{
|
|
CPLString osCmd = CPLSPrintf("pdftoppm -r %f -x %d -y %d -W %d -H %d -f %d -l %d \"%s\"",
|
|
dfDPI,
|
|
nReqXOff,
|
|
nReqYOff,
|
|
nReqXSize,
|
|
nReqYSize,
|
|
iPage, iPage,
|
|
osFilename.c_str());
|
|
|
|
if (osUserPwd.size() != 0)
|
|
{
|
|
osCmd += " -upw \"";
|
|
osCmd += osUserPwd;
|
|
osCmd += "\"";
|
|
}
|
|
|
|
CPLString osTmpFilenamePrefix = CPLGenerateTempFilename("pdf");
|
|
osTmpFilename = CPLSPrintf("%s-%d.ppm",
|
|
osTmpFilenamePrefix.c_str(),
|
|
iPage);
|
|
osCmd += CPLSPrintf(" \"%s\"", osTmpFilenamePrefix.c_str());
|
|
|
|
CPLDebug("PDF", "Running '%s'", osCmd.c_str());
|
|
nRet = CPLSystem(NULL, osCmd.c_str());
|
|
}
|
|
else
|
|
#endif // notdef
|
|
{
|
|
char** papszArgs = NULL;
|
|
papszArgs = CSLAddString(papszArgs, "pdftoppm");
|
|
papszArgs = CSLAddString(papszArgs, "-r");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%f", dfDPI));
|
|
papszArgs = CSLAddString(papszArgs, "-x");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXOff));
|
|
papszArgs = CSLAddString(papszArgs, "-y");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYOff));
|
|
papszArgs = CSLAddString(papszArgs, "-W");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXSize));
|
|
papszArgs = CSLAddString(papszArgs, "-H");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYSize));
|
|
papszArgs = CSLAddString(papszArgs, "-f");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", iPage));
|
|
papszArgs = CSLAddString(papszArgs, "-l");
|
|
papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", iPage));
|
|
if (osUserPwd.size() != 0)
|
|
{
|
|
papszArgs = CSLAddString(papszArgs, "-upw");
|
|
papszArgs = CSLAddString(papszArgs, osUserPwd.c_str());
|
|
}
|
|
papszArgs = CSLAddString(papszArgs, osFilename.c_str());
|
|
|
|
osTmpFilename = CPLSPrintf("/vsimem/pdf/temp_%p.ppm", this);
|
|
VSILFILE* fpOut = VSIFOpenL(osTmpFilename, "wb");
|
|
if( fpOut != NULL )
|
|
{
|
|
nRet = CPLSpawn(papszArgs, NULL, fpOut, FALSE);
|
|
VSIFCloseL(fpOut);
|
|
}
|
|
else
|
|
nRet = -1;
|
|
|
|
CSLDestroy(papszArgs);
|
|
}
|
|
|
|
if (nRet == 0)
|
|
{
|
|
GDALDataset* poDS = (GDALDataset*) GDALOpen(osTmpFilename, GA_ReadOnly);
|
|
if (poDS)
|
|
{
|
|
if (poDS->GetRasterCount() == 3)
|
|
{
|
|
eErr = poDS->RasterIO(GF_Read, 0, 0,
|
|
nReqXSize,
|
|
nReqYSize,
|
|
pabyData,
|
|
nReqXSize, nReqYSize,
|
|
GDT_Byte, 3, NULL,
|
|
nPixelSpace, nLineSpace, nBandSpace, NULL);
|
|
}
|
|
delete poDS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("PDF", "Ret code = %d", nRet);
|
|
bPdfToPpmFailed = TRUE;
|
|
eErr = CE_Failure;
|
|
}
|
|
VSIUnlink(osTmpFilename);
|
|
}
|
|
#endif
|
|
|
|
return eErr;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* PDFImageRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class PDFImageRasterBand : public PDFRasterBand
|
|
{
|
|
friend class PDFDataset;
|
|
|
|
public:
|
|
|
|
PDFImageRasterBand( PDFDataset *, int );
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
};
|
|
|
|
|
|
/************************************************************************/
|
|
/* PDFImageRasterBand() */
|
|
/************************************************************************/
|
|
|
|
PDFImageRasterBand::PDFImageRasterBand( PDFDataset *poDS, int nBand ) : PDFRasterBand(poDS, nBand)
|
|
|
|
{
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFImageRasterBand::IReadBlock( int CPL_UNUSED nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
PDFDataset *poGDS = (PDFDataset *) poDS;
|
|
CPLAssert(poGDS->poImageObj != NULL);
|
|
|
|
if (poGDS->bTried == FALSE)
|
|
{
|
|
int nBands = (poGDS->nBands == 1) ? 1 : 3;
|
|
poGDS->bTried = TRUE;
|
|
if (nBands == 3)
|
|
{
|
|
poGDS->pabyCachedData = (GByte*)VSIMalloc3(nBands, nRasterXSize, nRasterYSize);
|
|
if (poGDS->pabyCachedData == NULL)
|
|
return CE_Failure;
|
|
}
|
|
|
|
GDALPDFStream* poStream = poGDS->poImageObj->GetStream();
|
|
GByte* pabyStream = NULL;
|
|
|
|
if (poStream == NULL ||
|
|
poStream->GetLength() != nBands * nRasterXSize * nRasterYSize ||
|
|
(pabyStream = (GByte*) poStream->GetBytes()) == NULL)
|
|
{
|
|
VSIFree(poGDS->pabyCachedData);
|
|
poGDS->pabyCachedData = NULL;
|
|
return CE_Failure;
|
|
}
|
|
|
|
if (nBands == 3)
|
|
{
|
|
/* pixel interleaved to band interleaved */
|
|
for(int i = 0; i < nRasterXSize * nRasterYSize; i++)
|
|
{
|
|
poGDS->pabyCachedData[0 * nRasterXSize * nRasterYSize + i] = pabyStream[3 * i + 0];
|
|
poGDS->pabyCachedData[1 * nRasterXSize * nRasterYSize + i] = pabyStream[3 * i + 1];
|
|
poGDS->pabyCachedData[2 * nRasterXSize * nRasterYSize + i] = pabyStream[3 * i + 2];
|
|
}
|
|
VSIFree(pabyStream);
|
|
}
|
|
else
|
|
poGDS->pabyCachedData = pabyStream;
|
|
}
|
|
|
|
if (poGDS->pabyCachedData == NULL)
|
|
return CE_Failure;
|
|
|
|
if (nBand == 4)
|
|
memset(pImage, 255, nRasterXSize);
|
|
else
|
|
memcpy(pImage,
|
|
poGDS->pabyCachedData + (nBand - 1) * nRasterXSize * nRasterYSize + nBlockYOff * nRasterXSize,
|
|
nRasterXSize);
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~PDFDataset() */
|
|
/************************************************************************/
|
|
|
|
PDFDataset::PDFDataset()
|
|
{
|
|
bUsePoppler = FALSE;
|
|
#ifdef HAVE_POPPLER
|
|
poDocPoppler = NULL;
|
|
#endif
|
|
#ifdef HAVE_PODOFO
|
|
poDocPodofo = NULL;
|
|
bPdfToPpmFailed = FALSE;
|
|
#endif
|
|
poImageObj = NULL;
|
|
pszWKT = NULL;
|
|
dfDPI = GDAL_DEFAULT_DPI;
|
|
dfMaxArea = 0;
|
|
adfGeoTransform[0] = 0;
|
|
adfGeoTransform[1] = 1;
|
|
adfGeoTransform[2] = 0;
|
|
adfGeoTransform[3] = 0;
|
|
adfGeoTransform[4] = 0;
|
|
adfGeoTransform[5] = 1;
|
|
bHasCTM = FALSE;
|
|
bGeoTransformValid = FALSE;
|
|
nGCPCount = 0;
|
|
pasGCPList = NULL;
|
|
bProjDirty = FALSE;
|
|
bNeatLineDirty = FALSE;
|
|
bInfoDirty = FALSE;
|
|
bXMPDirty = FALSE;
|
|
bTried = FALSE;
|
|
pabyCachedData = NULL;
|
|
nLastBlockXOff = -1;
|
|
nLastBlockYOff = -1;
|
|
iPage = -1;
|
|
poNeatLine = NULL;
|
|
bUseOCG = FALSE;
|
|
poCatalogObject = NULL;
|
|
#ifdef HAVE_POPPLER
|
|
poCatalogObjectPoppler = NULL;
|
|
#endif
|
|
nBlockXSize = 0;
|
|
nBlockYSize = 0;
|
|
papszOpenOptions = NULL;
|
|
|
|
nLayers = 0;
|
|
papoLayers = NULL;
|
|
|
|
dfPageWidth = dfPageHeight = 0;
|
|
|
|
bSetStyle = CSLTestBoolean(CPLGetConfigOption("OGR_PDF_SET_STYLE", "YES"));
|
|
|
|
InitMapOperators();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PDFFreeDoc() */
|
|
/************************************************************************/
|
|
|
|
#ifdef HAVE_POPPLER
|
|
static void PDFFreeDoc(PDFDoc* poDoc)
|
|
{
|
|
if (poDoc)
|
|
{
|
|
/* hack to avoid potential cross heap issues on Win32 */
|
|
/* str is the VSIPDFFileStream object passed in the constructor of PDFDoc */
|
|
// NOTE: This is potentially very dangerous. See comment in VSIPDFFileStream::FillBuffer() */
|
|
delete poDoc->str;
|
|
poDoc->str = NULL;
|
|
|
|
delete poDoc;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* GetCatalog() */
|
|
/************************************************************************/
|
|
|
|
GDALPDFObject* PDFDataset::GetCatalog()
|
|
{
|
|
if (poCatalogObject)
|
|
return poCatalogObject;
|
|
|
|
#ifdef HAVE_POPPLER
|
|
if (bUsePoppler)
|
|
{
|
|
poCatalogObjectPoppler = new ObjectAutoFree;
|
|
poDocPoppler->getXRef()->getCatalog(poCatalogObjectPoppler);
|
|
if (!poCatalogObjectPoppler->isNull())
|
|
poCatalogObject = new GDALPDFObjectPoppler(poCatalogObjectPoppler, FALSE);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
{
|
|
int nCatalogNum = 0, nCatalogGen = 0;
|
|
VSILFILE* fp = VSIFOpenL(osFilename.c_str(), "rb");
|
|
if (fp != NULL)
|
|
{
|
|
GDALPDFWriter oWriter(fp, TRUE);
|
|
if (oWriter.ParseTrailerAndXRef())
|
|
{
|
|
nCatalogNum = oWriter.GetCatalogNum();
|
|
nCatalogGen = oWriter.GetCatalogGen();
|
|
}
|
|
oWriter.Close();
|
|
}
|
|
|
|
PoDoFo::PdfObject* poCatalogPodofo =
|
|
poDocPodofo->GetObjects().GetObject(PoDoFo::PdfReference(nCatalogNum, nCatalogGen));
|
|
if (poCatalogPodofo)
|
|
poCatalogObject = new GDALPDFObjectPodofo(poCatalogPodofo, poDocPodofo->GetObjects());
|
|
}
|
|
#endif
|
|
|
|
return poCatalogObject;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~PDFDataset() */
|
|
/************************************************************************/
|
|
|
|
PDFDataset::~PDFDataset()
|
|
{
|
|
CPLFree(pabyCachedData);
|
|
pabyCachedData = NULL;
|
|
|
|
delete poNeatLine;
|
|
poNeatLine = NULL;
|
|
|
|
/* Collect data necessary to update */
|
|
int nNum = poPageObj->GetRefNum();
|
|
int nGen = poPageObj->GetRefGen();
|
|
GDALPDFDictionaryRW* poPageDictCopy = NULL;
|
|
GDALPDFDictionaryRW* poCatalogDictCopy = NULL;
|
|
if (eAccess == GA_Update &&
|
|
(bProjDirty || bNeatLineDirty || bInfoDirty || bXMPDirty) &&
|
|
nNum != 0 &&
|
|
poPageObj != NULL &&
|
|
poPageObj->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
poPageDictCopy = poPageObj->GetDictionary()->Clone();
|
|
|
|
if (bXMPDirty)
|
|
{
|
|
/* We need the catalog because it points to the XMP Metadata object */
|
|
GetCatalog();
|
|
if (poCatalogObject && poCatalogObject->GetType() == PDFObjectType_Dictionary)
|
|
poCatalogDictCopy = poCatalogObject->GetDictionary()->Clone();
|
|
}
|
|
}
|
|
|
|
/* Close document (and file descriptor) to be able to open it */
|
|
/* in read-write mode afterwards */
|
|
delete poPageObj;
|
|
poPageObj = NULL;
|
|
delete poCatalogObject;
|
|
poCatalogObject = NULL;
|
|
#ifdef HAVE_POPPLER
|
|
delete poCatalogObjectPoppler;
|
|
PDFFreeDoc(poDocPoppler);
|
|
poDocPoppler = NULL;
|
|
#endif
|
|
#ifdef HAVE_PODOFO
|
|
delete poDocPodofo;
|
|
poDocPodofo = NULL;
|
|
#endif
|
|
|
|
/* Now do the update */
|
|
if (poPageDictCopy)
|
|
{
|
|
VSILFILE* fp = VSIFOpenL(osFilename, "rb+");
|
|
if (fp != NULL)
|
|
{
|
|
GDALPDFWriter oWriter(fp, TRUE);
|
|
if (oWriter.ParseTrailerAndXRef())
|
|
{
|
|
if ((bProjDirty || bNeatLineDirty) && poPageDictCopy != NULL)
|
|
oWriter.UpdateProj(this, dfDPI,
|
|
poPageDictCopy, nNum, nGen);
|
|
|
|
if (bInfoDirty)
|
|
oWriter.UpdateInfo(this);
|
|
|
|
if (bXMPDirty && poCatalogDictCopy != NULL)
|
|
oWriter.UpdateXMP(this, poCatalogDictCopy);
|
|
}
|
|
oWriter.Close();
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot open %s in update mode", osFilename.c_str());
|
|
}
|
|
}
|
|
delete poPageDictCopy;
|
|
poPageDictCopy = NULL;
|
|
delete poCatalogDictCopy;
|
|
poCatalogDictCopy = NULL;
|
|
|
|
if( nGCPCount > 0 )
|
|
{
|
|
GDALDeinitGCPs( nGCPCount, pasGCPList );
|
|
CPLFree( pasGCPList );
|
|
pasGCPList = NULL;
|
|
nGCPCount = 0;
|
|
}
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
CSLDestroy(papszOpenOptions);
|
|
|
|
CleanupIntermediateResources();
|
|
|
|
for(int i=0;i<nLayers;i++)
|
|
delete papoLayers[i];
|
|
CPLFree( papoLayers );
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* IRasterIO() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::IRasterIO( GDALRWFlag eRWFlag,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
void * pData, int nBufXSize, int nBufYSize,
|
|
GDALDataType eBufType,
|
|
int nBandCount, int *panBandMap,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GSpacing nBandSpace,
|
|
GDALRasterIOExtraArg* psExtraArg)
|
|
{
|
|
int nBandBlockXSize, nBandBlockYSize;
|
|
GetRasterBand(1)->GetBlockSize(&nBandBlockXSize, &nBandBlockYSize);
|
|
if( aiTiles.size() == 0 &&
|
|
eRWFlag == GF_Read && nXSize == nBufXSize && nYSize == nBufYSize &&
|
|
(nBufXSize > nBandBlockXSize || nBufYSize > nBandBlockYSize) &&
|
|
eBufType == GDT_Byte && nBandCount == nBands &&
|
|
(nBands >= 3 && panBandMap[0] == 1 && panBandMap[1] == 2 &&
|
|
panBandMap[2] == 3 && ( nBands == 3 || panBandMap[3] == 4)) )
|
|
{
|
|
int bReadPixels = TRUE;
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler && nBands == 4)
|
|
{
|
|
bReadPixels = FALSE;
|
|
}
|
|
#endif
|
|
if( bReadPixels )
|
|
return ReadPixels(nXOff, nYOff, nXSize, nYSize,
|
|
nPixelSpace, nLineSpace, nBandSpace, (GByte*)pData);
|
|
}
|
|
|
|
return GDALPamDataset::IRasterIO( eRWFlag,
|
|
nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize,
|
|
eBufType,
|
|
nBandCount, panBandMap,
|
|
nPixelSpace, nLineSpace, nBandSpace, psExtraArg );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::Identify( GDALOpenInfo * poOpenInfo )
|
|
{
|
|
if (strncmp(poOpenInfo->pszFilename, "PDF:", 4) == 0)
|
|
return TRUE;
|
|
if (strncmp(poOpenInfo->pszFilename, "PDF_IMAGE:", 10) == 0)
|
|
return TRUE;
|
|
|
|
if (poOpenInfo->nHeaderBytes < 128)
|
|
return FALSE;
|
|
|
|
return strncmp((const char*)poOpenInfo->pabyHeader, "%PDF", 4) == 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PDFDatasetErrorFunction() */
|
|
/************************************************************************/
|
|
|
|
#ifdef HAVE_POPPLER
|
|
|
|
static void PDFDatasetErrorFunctionCommon(const CPLString& osError)
|
|
{
|
|
if (strcmp(osError.c_str(), "Incorrect password") == 0)
|
|
return;
|
|
/* Reported on newer USGS GeoPDF */
|
|
if (strcmp(osError.c_str(), "Couldn't find group for reference to set OFF") == 0)
|
|
{
|
|
CPLDebug("PDF", "%s", osError.c_str());
|
|
return;
|
|
}
|
|
|
|
CPLError(CE_Failure, CPLE_AppDefined, "%s", osError.c_str());
|
|
}
|
|
|
|
#ifdef POPPLER_0_20_OR_LATER
|
|
static void PDFDatasetErrorFunction(CPL_UNUSED void* userData, CPL_UNUSED ErrorCategory eErrCatagory,
|
|
#ifdef POPPLER_0_23_OR_LATER
|
|
Goffset nPos,
|
|
#else
|
|
int nPos,
|
|
#endif
|
|
char *pszMsg)
|
|
{
|
|
CPLString osError;
|
|
|
|
if (nPos >= 0)
|
|
osError.Printf("Pos = %d, ", (int)nPos);
|
|
osError += pszMsg;
|
|
PDFDatasetErrorFunctionCommon(osError);
|
|
}
|
|
#else
|
|
static void PDFDatasetErrorFunction(int nPos, char *pszMsg, va_list args)
|
|
{
|
|
CPLString osError;
|
|
|
|
if (nPos >= 0)
|
|
osError.Printf("Pos = %d, ", nPos);
|
|
osError += CPLString().vPrintf(pszMsg, args);
|
|
PDFDatasetErrorFunctionCommon(osError);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* GDALPDFParseStreamContentOnlyDrawForm() */
|
|
/************************************************************************/
|
|
|
|
static
|
|
CPLString GDALPDFParseStreamContentOnlyDrawForm(const char* pszContent)
|
|
{
|
|
CPLString osToken;
|
|
char ch;
|
|
int nCurIdx = 0;
|
|
CPLString osCurrentForm;
|
|
|
|
//CPLDebug("PDF", "content = %s", pszContent);
|
|
|
|
while((ch = *pszContent) != '\0')
|
|
{
|
|
if (ch == '%')
|
|
{
|
|
/* Skip comments until end-of-line */
|
|
while((ch = *pszContent) != '\0')
|
|
{
|
|
if (ch == '\r' || ch == '\n')
|
|
break;
|
|
pszContent ++;
|
|
}
|
|
if (ch == 0)
|
|
break;
|
|
}
|
|
else if (ch == ' ' || ch == '\r' || ch == '\n')
|
|
{
|
|
if (osToken.size())
|
|
{
|
|
if (nCurIdx == 0 && osToken[0] == '/')
|
|
{
|
|
osCurrentForm = osToken.substr(1);
|
|
nCurIdx ++;
|
|
}
|
|
else if (nCurIdx == 1 && osToken == "Do")
|
|
{
|
|
nCurIdx ++;
|
|
}
|
|
else
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
osToken = "";
|
|
}
|
|
else
|
|
osToken += ch;
|
|
pszContent ++;
|
|
}
|
|
|
|
return osCurrentForm;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALPDFParseStreamContent() */
|
|
/************************************************************************/
|
|
|
|
typedef enum
|
|
{
|
|
STATE_INIT,
|
|
STATE_AFTER_q,
|
|
STATE_AFTER_cm,
|
|
STATE_AFTER_Do
|
|
} PDFStreamState;
|
|
|
|
/* This parser is reduced to understanding sequences that draw rasters, such as :
|
|
q
|
|
scaleX 0 0 scaleY translateX translateY cm
|
|
/ImXXX Do
|
|
Q
|
|
|
|
All other sequences will abort the parsing.
|
|
|
|
Returns TRUE if the stream only contains images.
|
|
*/
|
|
|
|
static
|
|
int GDALPDFParseStreamContent(const char* pszContent,
|
|
GDALPDFDictionary* poXObjectDict,
|
|
double* pdfDPI,
|
|
int* pbDPISet,
|
|
int* pnBands,
|
|
std::vector<GDALPDFTileDesc>& asTiles,
|
|
int bAcceptRotationTerms)
|
|
{
|
|
CPLString osToken;
|
|
char ch;
|
|
PDFStreamState nState = STATE_INIT;
|
|
int nCurIdx = 0;
|
|
double adfVals[6];
|
|
CPLString osCurrentImage;
|
|
|
|
double dfDPI = DEFAULT_DPI;
|
|
*pbDPISet = FALSE;
|
|
|
|
while((ch = *pszContent) != '\0')
|
|
{
|
|
if (ch == '%')
|
|
{
|
|
/* Skip comments until end-of-line */
|
|
while((ch = *pszContent) != '\0')
|
|
{
|
|
if (ch == '\r' || ch == '\n')
|
|
break;
|
|
pszContent ++;
|
|
}
|
|
if (ch == 0)
|
|
break;
|
|
}
|
|
else if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
|
|
{
|
|
if (osToken.size())
|
|
{
|
|
if (nState == STATE_INIT)
|
|
{
|
|
if (osToken == "q")
|
|
{
|
|
nState = STATE_AFTER_q;
|
|
nCurIdx = 0;
|
|
}
|
|
else if (osToken != "Q")
|
|
return FALSE;
|
|
}
|
|
else if (nState == STATE_AFTER_q)
|
|
{
|
|
if (osToken == "q")
|
|
{
|
|
// ignore
|
|
}
|
|
else if (nCurIdx < 6)
|
|
{
|
|
adfVals[nCurIdx ++] = CPLAtof(osToken);
|
|
}
|
|
else if (nCurIdx == 6 && osToken == "cm")
|
|
{
|
|
nState = STATE_AFTER_cm;
|
|
nCurIdx = 0;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else if (nState == STATE_AFTER_cm)
|
|
{
|
|
if (nCurIdx == 0 && osToken[0] == '/')
|
|
{
|
|
osCurrentImage = osToken.substr(1);
|
|
}
|
|
else if (osToken == "Do")
|
|
{
|
|
nState = STATE_AFTER_Do;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else if (nState == STATE_AFTER_Do)
|
|
{
|
|
if (osToken == "Q")
|
|
{
|
|
GDALPDFObject* poImage = poXObjectDict->Get(osCurrentImage);
|
|
if (poImage != NULL && poImage->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFTileDesc sTile;
|
|
GDALPDFDictionary* poImageDict = poImage->GetDictionary();
|
|
GDALPDFObject* poWidth = poImageDict->Get("Width");
|
|
GDALPDFObject* poHeight = poImageDict->Get("Height");
|
|
GDALPDFObject* poColorSpace = poImageDict->Get("ColorSpace");
|
|
GDALPDFObject* poSMask = poImageDict->Get("SMask");
|
|
if (poColorSpace && poColorSpace->GetType() == PDFObjectType_Name)
|
|
{
|
|
if (poColorSpace->GetName() == "DeviceRGB")
|
|
{
|
|
sTile.nBands = 3;
|
|
if ( *pnBands < 3)
|
|
*pnBands = 3;
|
|
}
|
|
else if (poColorSpace->GetName() == "DeviceGray")
|
|
{
|
|
sTile.nBands = 1;
|
|
if ( *pnBands < 1)
|
|
*pnBands = 1;
|
|
}
|
|
else
|
|
sTile.nBands = 0;
|
|
}
|
|
if ( poSMask != NULL )
|
|
*pnBands = 4;
|
|
|
|
if (poWidth && poHeight && ((bAcceptRotationTerms && adfVals[1] == -adfVals[2]) ||
|
|
(!bAcceptRotationTerms && adfVals[1] == 0.0 && adfVals[2] == 0.0)))
|
|
{
|
|
double dfWidth = Get(poWidth);
|
|
double dfHeight = Get(poHeight);
|
|
double dfScaleX = adfVals[0];
|
|
double dfScaleY = adfVals[3];
|
|
double dfDPI_X = ROUND_TO_INT_IF_CLOSE(dfWidth / dfScaleX * DEFAULT_DPI, 1e-3);
|
|
double dfDPI_Y = ROUND_TO_INT_IF_CLOSE(dfHeight / dfScaleY * DEFAULT_DPI, 1e-3);
|
|
//CPLDebug("PDF", "Image %s, width = %.16g, height = %.16g, scaleX = %.16g, scaleY = %.16g --> DPI_X = %.16g, DPI_Y = %.16g",
|
|
// osCurrentImage.c_str(), dfWidth, dfHeight, dfScaleX, dfScaleY, dfDPI_X, dfDPI_Y);
|
|
if (dfDPI_X > dfDPI) dfDPI = dfDPI_X;
|
|
if (dfDPI_Y > dfDPI) dfDPI = dfDPI_Y;
|
|
|
|
memcpy(&(sTile.adfCM), adfVals, 6 * sizeof(double));
|
|
sTile.poImage = poImage;
|
|
sTile.dfWidth = dfWidth;
|
|
sTile.dfHeight = dfHeight;
|
|
asTiles.push_back(sTile);
|
|
|
|
*pbDPISet = TRUE;
|
|
*pdfDPI = dfDPI;
|
|
}
|
|
}
|
|
nState = STATE_INIT;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
osToken = "";
|
|
}
|
|
else
|
|
osToken += ch;
|
|
pszContent ++;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CheckTiledRaster() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::CheckTiledRaster()
|
|
{
|
|
size_t i;
|
|
int nBlockXSize = 0, nBlockYSize = 0;
|
|
const double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
|
|
|
|
/* First pass : check that all tiles have same DPI, */
|
|
/* are contained entirely in the raster size, */
|
|
/* and determine the block size */
|
|
for(i=0; i<asTiles.size(); i++)
|
|
{
|
|
double dfDrawWidth = asTiles[i].adfCM[0] * dfUserUnit;
|
|
double dfDrawHeight = asTiles[i].adfCM[3] * dfUserUnit;
|
|
double dfX = asTiles[i].adfCM[4] * dfUserUnit;
|
|
double dfY = asTiles[i].adfCM[5] * dfUserUnit;
|
|
int nX = (int)(dfX+0.1);
|
|
int nY = (int)(dfY+0.1);
|
|
int nWidth = (int)(asTiles[i].dfWidth + 1e-8);
|
|
int nHeight = (int)(asTiles[i].dfHeight + 1e-8);
|
|
|
|
GDALPDFDictionary* poImageDict = asTiles[i].poImage->GetDictionary();
|
|
GDALPDFObject* poBitsPerComponent = poImageDict->Get("BitsPerComponent");
|
|
GDALPDFObject* poColorSpace = poImageDict->Get("ColorSpace");
|
|
GDALPDFObject* poFilter = poImageDict->Get("Filter");
|
|
|
|
/* Podofo cannot uncompress JPEG2000 streams */
|
|
if( !bUsePoppler && poFilter != NULL &&
|
|
poFilter->GetType() == PDFObjectType_Name &&
|
|
poFilter->GetName() == "JPXDecode" )
|
|
{
|
|
CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
|
|
(int)i);
|
|
return FALSE;
|
|
}
|
|
|
|
if( poBitsPerComponent == NULL ||
|
|
Get(poBitsPerComponent) != 8 ||
|
|
poColorSpace == NULL ||
|
|
poColorSpace->GetType() != PDFObjectType_Name ||
|
|
(poColorSpace->GetName() != "DeviceRGB" &&
|
|
poColorSpace->GetName() != "DeviceGray") )
|
|
{
|
|
CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
|
|
(int)i);
|
|
return FALSE;
|
|
}
|
|
|
|
if( fabs(dfDrawWidth - asTiles[i].dfWidth) > 1e-2 ||
|
|
fabs(dfDrawHeight - asTiles[i].dfHeight) > 1e-2 ||
|
|
fabs(nWidth - asTiles[i].dfWidth) > 1e-8 ||
|
|
fabs(nHeight - asTiles[i].dfHeight) > 1e-8 ||
|
|
fabs(nX - dfX) > 1e-1 ||
|
|
fabs(nY - dfY) > 1e-1 ||
|
|
nX < 0 || nY < 0 || nX + nWidth > nRasterXSize ||
|
|
nY >= nRasterYSize )
|
|
{
|
|
CPLDebug("PDF", "Tile %d : %f %f %f %f %f %f",
|
|
(int)i, dfX, dfY, dfDrawWidth, dfDrawHeight,
|
|
asTiles[i].dfWidth, asTiles[i].dfHeight);
|
|
return FALSE;
|
|
}
|
|
if( nBlockXSize == 0 && nBlockYSize == 0 &&
|
|
nX == 0 && nY != 0 )
|
|
{
|
|
nBlockXSize = nWidth;
|
|
nBlockYSize = nHeight;
|
|
}
|
|
}
|
|
if( nBlockXSize <= 0 || nBlockYSize <= 0 || nBlockXSize > 2048 || nBlockYSize > 2048 )
|
|
return FALSE;
|
|
|
|
int nXBlocks = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
|
|
int nYBlocks = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
|
|
|
|
/* Second pass to determine that all tiles are properly aligned on block size */
|
|
for(i=0; i<asTiles.size(); i++)
|
|
{
|
|
double dfX = asTiles[i].adfCM[4] * dfUserUnit;
|
|
double dfY = asTiles[i].adfCM[5] * dfUserUnit;
|
|
int nX = (int)(dfX+0.1);
|
|
int nY = (int)(dfY+0.1);
|
|
int nWidth = (int)(asTiles[i].dfWidth + 1e-8);
|
|
int nHeight = (int)(asTiles[i].dfHeight + 1e-8);
|
|
int bOK = TRUE;
|
|
int nBlockXOff = nX / nBlockXSize;
|
|
if( (nX % nBlockXSize) != 0 )
|
|
bOK = FALSE;
|
|
if( nBlockXOff < nXBlocks - 1 && nWidth != nBlockXSize )
|
|
bOK = FALSE;
|
|
if( nBlockXOff == nXBlocks - 1 && nX + nWidth != nRasterXSize )
|
|
bOK = FALSE;
|
|
|
|
if( nY > 0 && nHeight != nBlockYSize )
|
|
bOK = FALSE;
|
|
if( nY == 0 && nHeight != nRasterYSize - (nYBlocks - 1) * nBlockYSize)
|
|
bOK = FALSE;
|
|
|
|
if( !bOK )
|
|
{
|
|
CPLDebug("PDF", "Tile %d : %d %d %d %d",
|
|
(int)i, nX, nY, nWidth, nHeight);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Third pass to set the aiTiles array */
|
|
aiTiles.resize(nXBlocks * nYBlocks, -1);
|
|
for(i=0; i<asTiles.size(); i++)
|
|
{
|
|
double dfX = asTiles[i].adfCM[4] * dfUserUnit;
|
|
double dfY = asTiles[i].adfCM[5] * dfUserUnit;
|
|
int nHeight = (int)(asTiles[i].dfHeight + 1e-8);
|
|
int nX = (int)(dfX+0.1);
|
|
int nY = nRasterYSize - ((int)(dfY+0.1) + nHeight);
|
|
int nBlockXOff = nX / nBlockXSize;
|
|
int nBlockYOff = nY / nBlockYSize;
|
|
aiTiles[ nBlockYOff * nXBlocks + nBlockXOff ] = (int) i;
|
|
}
|
|
|
|
this->nBlockXSize = nBlockXSize;
|
|
this->nBlockYSize = nBlockYSize;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GuessDPI() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::GuessDPI(GDALPDFDictionary* poPageDict, int* pnBands)
|
|
{
|
|
const char* pszDPI = GetOption(papszOpenOptions, "DPI", NULL);
|
|
if (pszDPI != NULL)
|
|
{
|
|
dfDPI = CPLAtof(pszDPI);
|
|
}
|
|
else
|
|
{
|
|
/* Try to get a better value from the images that are drawn */
|
|
/* Very simplistic logic. Will only work for raster only PDF */
|
|
|
|
GDALPDFObject* poContents = poPageDict->Get("Contents");
|
|
if (poContents != NULL && poContents->GetType() == PDFObjectType_Array)
|
|
{
|
|
GDALPDFArray* poContentsArray = poContents->GetArray();
|
|
if (poContentsArray->GetLength() == 1)
|
|
{
|
|
poContents = poContentsArray->Get(0);
|
|
}
|
|
}
|
|
|
|
GDALPDFObject* poXObject = poPageDict->LookupObject("Resources.XObject");
|
|
if (poContents != NULL &&
|
|
poContents->GetType() == PDFObjectType_Dictionary &&
|
|
poXObject != NULL &&
|
|
poXObject->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poXObjectDict = poXObject->GetDictionary();
|
|
GDALPDFDictionary* poContentDict = poXObjectDict;
|
|
GDALPDFStream* poPageStream = poContents->GetStream();
|
|
if (poPageStream != NULL)
|
|
{
|
|
char* pszContent = NULL;
|
|
int nLength = poPageStream->GetLength();
|
|
int bResetTiles = FALSE;
|
|
double dfScaleDPI = 1.0;
|
|
|
|
if( nLength < 100000 )
|
|
{
|
|
CPLString osForm;
|
|
pszContent = poPageStream->GetBytes();
|
|
if( pszContent != NULL )
|
|
{
|
|
#ifdef DEBUG
|
|
const char* pszDumpStream = CPLGetConfigOption("PDF_DUMP_STREAM", NULL);
|
|
if( pszDumpStream != NULL )
|
|
{
|
|
VSILFILE* fpDump = VSIFOpenL(pszDumpStream, "wb");
|
|
if( fpDump )
|
|
{
|
|
VSIFWriteL(pszContent, 1, nLength, fpDump);
|
|
VSIFCloseL(fpDump);
|
|
}
|
|
}
|
|
#endif // DEBUG
|
|
osForm = GDALPDFParseStreamContentOnlyDrawForm(pszContent);
|
|
if (osForm.size() == 0)
|
|
{
|
|
/* Special case for USGS Topo PDF, like CA_Hollywood_20090811_OM_geo.pdf */
|
|
const char* pszOGCDo = strstr(pszContent, " /XO1 Do");
|
|
if( pszOGCDo )
|
|
{
|
|
const char* pszcm = strstr(pszContent, " cm ");
|
|
if( pszcm != NULL && pszcm < pszOGCDo )
|
|
{
|
|
const char* pszNextcm = strstr(pszcm + 2, "cm");
|
|
if( pszNextcm == NULL || pszNextcm > pszOGCDo )
|
|
{
|
|
const char* pszIter = pszcm;
|
|
while( pszIter > pszContent )
|
|
{
|
|
if( (*pszIter >= '0' && *pszIter <= '9') ||
|
|
*pszIter == '-' ||
|
|
*pszIter == '.' ||
|
|
*pszIter == ' ' )
|
|
pszIter --;
|
|
else
|
|
{
|
|
pszIter ++;
|
|
break;
|
|
}
|
|
}
|
|
CPLString oscm(pszIter);
|
|
oscm.resize(pszcm - pszIter);
|
|
char** papszTokens = CSLTokenizeString(oscm);
|
|
double dfScaleX = -1, dfScaleY = -2;
|
|
if( CSLCount(papszTokens) == 6 )
|
|
{
|
|
dfScaleX = CPLAtof(papszTokens[0]);
|
|
dfScaleY = CPLAtof(papszTokens[3]);
|
|
}
|
|
CSLDestroy(papszTokens);
|
|
if( dfScaleX == dfScaleY && dfScaleX > 0.0 )
|
|
{
|
|
osForm = "XO1";
|
|
bResetTiles = TRUE;
|
|
dfScaleDPI = 1.0 / dfScaleX;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
osForm = "XO1";
|
|
bResetTiles = TRUE;
|
|
}
|
|
}
|
|
/* Special case for USGS Topo PDF, like CA_Sacramento_East_20120308_TM_geo.pdf */
|
|
else
|
|
{
|
|
CPLString osOCG = FindLayerOCG(poPageDict, "Orthoimage");
|
|
if( osOCG.size() )
|
|
{
|
|
const char* pszBDCLookup = CPLSPrintf("/OC /%s BDC", osOCG.c_str());
|
|
const char* pszBDC = strstr(pszContent, pszBDCLookup);
|
|
if( pszBDC != NULL )
|
|
{
|
|
const char* pszIter = pszBDC + strlen(pszBDCLookup);
|
|
while( *pszIter != '\0' )
|
|
{
|
|
if( *pszIter == 13 || *pszIter == 10 ||
|
|
*pszIter == ' '|| *pszIter == 'q' )
|
|
pszIter ++;
|
|
else
|
|
break;
|
|
}
|
|
if( strncmp(pszIter, "1 0 0 1 0 0 cm\n",
|
|
strlen( "1 0 0 1 0 0 cm\n" )) == 0 )
|
|
pszIter += strlen("1 0 0 1 0 0 cm\n");
|
|
if( *pszIter == '/' )
|
|
{
|
|
pszIter ++;
|
|
const char* pszDo = strstr(pszIter, " Do");
|
|
if( pszDo != NULL )
|
|
{
|
|
osForm = pszIter;
|
|
osForm.resize(pszDo - pszIter);
|
|
bResetTiles = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (osForm.size())
|
|
{
|
|
CPLFree(pszContent);
|
|
pszContent = NULL;
|
|
|
|
GDALPDFObject* poObjForm = poXObjectDict->Get(osForm);
|
|
if (poObjForm != NULL &&
|
|
poObjForm->GetType() == PDFObjectType_Dictionary &&
|
|
(poPageStream = poObjForm->GetStream()) != NULL)
|
|
{
|
|
GDALPDFDictionary* poObjFormDict = poObjForm->GetDictionary();
|
|
GDALPDFObject* poSubtype;
|
|
if ((poSubtype = poObjFormDict->Get("Subtype")) != NULL &&
|
|
poSubtype->GetType() == PDFObjectType_Name &&
|
|
poSubtype->GetName() == "Form")
|
|
{
|
|
int nLength = poPageStream->GetLength();
|
|
if( nLength < 100000 )
|
|
{
|
|
pszContent = poPageStream->GetBytes();
|
|
|
|
GDALPDFObject* poXObject2 = poObjFormDict->LookupObject("Resources.XObject");
|
|
if( poXObject2 != NULL &&
|
|
poXObject2->GetType() == PDFObjectType_Dictionary )
|
|
poContentDict = poXObject2->GetDictionary();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pszContent != NULL)
|
|
{
|
|
int bDPISet = FALSE;
|
|
|
|
const char* pszContentToParse = pszContent;
|
|
if( bResetTiles )
|
|
{
|
|
while( *pszContentToParse != '\0' )
|
|
{
|
|
if( *pszContentToParse == 13 || *pszContentToParse == 10 ||
|
|
*pszContentToParse == ' ' ||
|
|
(*pszContentToParse >= '0' && *pszContentToParse <= '9') ||
|
|
*pszContentToParse == '.' ||
|
|
*pszContentToParse == '-' ||
|
|
*pszContentToParse == 'l' ||
|
|
*pszContentToParse == 'm' ||
|
|
*pszContentToParse == 'n' ||
|
|
*pszContentToParse == 'W' )
|
|
pszContentToParse ++;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
GDALPDFParseStreamContent(pszContentToParse,
|
|
poContentDict,
|
|
&(dfDPI),
|
|
&bDPISet,
|
|
pnBands,
|
|
asTiles,
|
|
bResetTiles);
|
|
CPLFree(pszContent);
|
|
if (bDPISet)
|
|
{
|
|
dfDPI *= dfScaleDPI;
|
|
|
|
CPLDebug("PDF", "DPI guessed from contents stream = %.16g", dfDPI);
|
|
SetMetadataItem("DPI", CPLSPrintf("%.16g", dfDPI));
|
|
if( bResetTiles )
|
|
asTiles.resize(0);
|
|
}
|
|
else
|
|
asTiles.resize(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
GDALPDFObject* poUserUnit = NULL;
|
|
if ( (poUserUnit = poPageDict->Get("UserUnit")) != NULL &&
|
|
(poUserUnit->GetType() == PDFObjectType_Int ||
|
|
poUserUnit->GetType() == PDFObjectType_Real) )
|
|
{
|
|
dfDPI = ROUND_TO_INT_IF_CLOSE(Get(poUserUnit) * DEFAULT_DPI);
|
|
CPLDebug("PDF", "Found UserUnit in Page --> DPI = %.16g", dfDPI);
|
|
SetMetadataItem("DPI", CPLSPrintf("%.16g", dfDPI));
|
|
}
|
|
}
|
|
|
|
if (dfDPI < 1 || dfDPI > 7200)
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Invalid value for GDAL_PDF_DPI. Using default value instead");
|
|
dfDPI = GDAL_DEFAULT_DPI;
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindXMP() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::FindXMP(GDALPDFObject* poObj)
|
|
{
|
|
if (poObj->GetType() != PDFObjectType_Dictionary)
|
|
return;
|
|
|
|
GDALPDFDictionary* poDict = poObj->GetDictionary();
|
|
GDALPDFObject* poType = poDict->Get("Type");
|
|
GDALPDFObject* poSubtype = poDict->Get("Subtype");
|
|
if (poType == NULL ||
|
|
poType->GetType() != PDFObjectType_Name ||
|
|
poType->GetName() != "Metadata" ||
|
|
poSubtype == NULL ||
|
|
poSubtype->GetType() != PDFObjectType_Name ||
|
|
poSubtype->GetName() != "XML")
|
|
{
|
|
return;
|
|
}
|
|
|
|
GDALPDFStream* poStream = poObj->GetStream();
|
|
if (poStream == NULL)
|
|
return;
|
|
|
|
char* pszContent = poStream->GetBytes();
|
|
int nLength = (int)poStream->GetLength();
|
|
if (pszContent != NULL && nLength > 15 &&
|
|
strncmp(pszContent, "<?xpacket begin=", strlen("<?xpacket begin=")) == 0)
|
|
{
|
|
char *apszMDList[2];
|
|
apszMDList[0] = pszContent;
|
|
apszMDList[1] = NULL;
|
|
SetMetadata(apszMDList, "xml:XMP");
|
|
}
|
|
CPLFree(pszContent);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseInfo() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::ParseInfo(GDALPDFObject* poInfoObj)
|
|
{
|
|
if (poInfoObj->GetType() != PDFObjectType_Dictionary)
|
|
return;
|
|
|
|
GDALPDFDictionary* poInfoObjDict = poInfoObj->GetDictionary();
|
|
GDALPDFObject* poItem = NULL;
|
|
int bOneMDISet = FALSE;
|
|
if ((poItem = poInfoObjDict->Get("Author")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
SetMetadataItem("AUTHOR", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("Creator")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
SetMetadataItem("CREATOR", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("Keywords")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
SetMetadataItem("KEYWORDS", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("Subject")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
SetMetadataItem("SUBJECT", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("Title")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
SetMetadataItem("TITLE", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("Producer")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
if (bOneMDISet || poItem->GetString() != "PoDoFo - http://podofo.sf.net")
|
|
{
|
|
SetMetadataItem("PRODUCER", poItem->GetString().c_str());
|
|
bOneMDISet = TRUE;
|
|
}
|
|
}
|
|
if ((poItem = poInfoObjDict->Get("CreationDate")) != NULL &&
|
|
poItem->GetType() == PDFObjectType_String)
|
|
{
|
|
if (bOneMDISet)
|
|
SetMetadataItem("CREATION_DATE", poItem->GetString().c_str());
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_POPPLER
|
|
|
|
/************************************************************************/
|
|
/* AddLayer() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::AddLayer(const char* pszLayerName, OptionalContentGroup* ocg)
|
|
{
|
|
int nNewIndex = osLayerList.size() /*/ 2*/;
|
|
|
|
if (nNewIndex == 100)
|
|
{
|
|
CPLStringList osNewLayerList;
|
|
for(int i=0;i<100;i++)
|
|
{
|
|
osNewLayerList.AddNameValue(CPLSPrintf("LAYER_%03d_NAME", i),
|
|
osLayerList[/*2 * */ i] + strlen("LAYER_00_NAME="));
|
|
/*osNewLayerList.AddNameValue(CPLSPrintf("LAYER_%03d_INIT_STATE", i),
|
|
osLayerList[i * 2 + 1] + strlen("LAYER_00_INIT_STATE="));*/
|
|
}
|
|
osLayerList = osNewLayerList;
|
|
}
|
|
|
|
char szFormatName[64];
|
|
/* char szFormatInitState[64]; */
|
|
sprintf(szFormatName, "LAYER_%%0%dd_NAME", nNewIndex >= 100 ? 3 : 2);
|
|
/* sprintf(szFormatInitState, "LAYER_%%0%dd_INIT_STATE", nNewIndex >= 100 ? 3 : 2); */
|
|
|
|
osLayerList.AddNameValue(CPLSPrintf(szFormatName, nNewIndex),
|
|
pszLayerName);
|
|
/*osLayerList.AddNameValue(CPLSPrintf(szFormatInitState, nNewIndex),
|
|
(ocg == NULL || ocg->getState() == OptionalContentGroup::On) ? "ON" : "OFF");*/
|
|
oLayerOCGMap[pszLayerName] = ocg;
|
|
|
|
//if (ocg != NULL && ocg->getState() == OptionalContentGroup::Off)
|
|
// bUseOCG = TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ExploreLayers() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::ExploreLayers(GDALPDFArray* poArray,
|
|
int nRecLevel,
|
|
CPLString osTopLayer)
|
|
{
|
|
if( nRecLevel == 16 )
|
|
return;
|
|
|
|
int nLength = poArray->GetLength();
|
|
CPLString osCurLayer;
|
|
for(int i=0;i<nLength;i++)
|
|
{
|
|
GDALPDFObject* poObj = poArray->Get(i);
|
|
if (i == 0 && poObj->GetType() == PDFObjectType_String)
|
|
{
|
|
CPLString osName = PDFSanitizeLayerName(poObj->GetString().c_str());
|
|
if (osTopLayer.size())
|
|
osTopLayer = osTopLayer + "." + osName;
|
|
else
|
|
osTopLayer = osName;
|
|
AddLayer(osTopLayer.c_str(), NULL);
|
|
}
|
|
else if (poObj->GetType() == PDFObjectType_Array)
|
|
{
|
|
ExploreLayers(poObj->GetArray(), nRecLevel + 1, osCurLayer);
|
|
osCurLayer = "";
|
|
}
|
|
else if (poObj->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poDict = poObj->GetDictionary();
|
|
GDALPDFObject* poName = poDict->Get("Name");
|
|
if (poName != NULL && poName->GetType() == PDFObjectType_String)
|
|
{
|
|
CPLString osName = PDFSanitizeLayerName(poName->GetString().c_str());
|
|
if (osTopLayer.size())
|
|
osCurLayer = osTopLayer + "." + osName;
|
|
else
|
|
osCurLayer = osName;
|
|
//CPLDebug("PDF", "Layer %s", osCurLayer.c_str());
|
|
|
|
OCGs* optContentConfig = poDocPoppler->getOptContentConfig();
|
|
struct Ref r;
|
|
r.num = poObj->GetRefNum();
|
|
r.gen = poObj->GetRefGen();
|
|
OptionalContentGroup* ocg = optContentConfig->findOcgByRef(r);
|
|
if (ocg)
|
|
{
|
|
AddLayer(osCurLayer.c_str(), ocg);
|
|
osLayerWithRefList.AddString(
|
|
CPLSPrintf("%s %d %d", osCurLayer.c_str(), r.num, r.gen));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindLayers() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::FindLayers()
|
|
{
|
|
OCGs* optContentConfig = poDocPoppler->getOptContentConfig();
|
|
if (optContentConfig == NULL || !optContentConfig->isOk() )
|
|
return;
|
|
|
|
Array* array = optContentConfig->getOrderArray();
|
|
if (array)
|
|
{
|
|
GDALPDFArray* poArray = GDALPDFCreateArray(array);
|
|
ExploreLayers(poArray, 0);
|
|
delete poArray;
|
|
}
|
|
else
|
|
{
|
|
GooList* ocgList = optContentConfig->getOCGs();
|
|
for(int i=0;i<ocgList->getLength();i++)
|
|
{
|
|
OptionalContentGroup* ocg = (OptionalContentGroup*) ocgList->get(i);
|
|
if( ocg != NULL && ocg->getName() != NULL )
|
|
AddLayer((const char*)ocg->getName()->getCString(), ocg);
|
|
}
|
|
}
|
|
|
|
oMDMD.SetMetadata(osLayerList.List(), "LAYERS");
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* TurnLayersOnOff() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::TurnLayersOnOff()
|
|
{
|
|
OCGs* optContentConfig = poDocPoppler->getOptContentConfig();
|
|
if (optContentConfig == NULL || !optContentConfig->isOk() )
|
|
return;
|
|
|
|
// Which layers to turn ON ?
|
|
const char* pszLayers = GetOption(papszOpenOptions, "LAYERS", NULL);
|
|
if (pszLayers)
|
|
{
|
|
int i;
|
|
int bAll = EQUAL(pszLayers, "ALL");
|
|
GooList* ocgList = optContentConfig->getOCGs();
|
|
for(i=0;i<ocgList->getLength();i++)
|
|
{
|
|
OptionalContentGroup* ocg = (OptionalContentGroup*) ocgList->get(i);
|
|
ocg->setState( (bAll) ? OptionalContentGroup::On : OptionalContentGroup::Off );
|
|
}
|
|
|
|
char** papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
|
|
for(i=0;!bAll && papszLayers[i] != NULL;i++)
|
|
{
|
|
std::map<CPLString, OptionalContentGroup*>::iterator oIter =
|
|
oLayerOCGMap.find(papszLayers[i]);
|
|
if (oIter != oLayerOCGMap.end())
|
|
{
|
|
if (oIter->second)
|
|
{
|
|
//CPLDebug("PDF", "Turn '%s' on", papszLayers[i]);
|
|
oIter->second->setState(OptionalContentGroup::On);
|
|
}
|
|
|
|
// Turn child layers on, unless there's one of them explicitly listed
|
|
// in the list.
|
|
size_t nLen = strlen(papszLayers[i]);
|
|
int bFoundChildLayer = FALSE;
|
|
oIter = oLayerOCGMap.begin();
|
|
for( ; oIter != oLayerOCGMap.end() && !bFoundChildLayer; oIter ++)
|
|
{
|
|
if (oIter->first.size() > nLen &&
|
|
strncmp(oIter->first.c_str(), papszLayers[i], nLen) == 0 &&
|
|
oIter->first[nLen] == '.')
|
|
{
|
|
for(int j=0;papszLayers[j] != NULL;j++)
|
|
{
|
|
if (strcmp(papszLayers[j], oIter->first.c_str()) == 0)
|
|
bFoundChildLayer = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !bFoundChildLayer )
|
|
{
|
|
oIter = oLayerOCGMap.begin();
|
|
for( ; oIter != oLayerOCGMap.end() && !bFoundChildLayer; oIter ++)
|
|
{
|
|
if (oIter->first.size() > nLen &&
|
|
strncmp(oIter->first.c_str(), papszLayers[i], nLen) == 0 &&
|
|
oIter->first[nLen] == '.')
|
|
{
|
|
if (oIter->second)
|
|
{
|
|
//CPLDebug("PDF", "Turn '%s' on too", oIter->first.c_str());
|
|
oIter->second->setState(OptionalContentGroup::On);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Turn parent layers on too
|
|
char* pszLastDot;
|
|
while( (pszLastDot = strrchr(papszLayers[i], '.')) != NULL)
|
|
{
|
|
*pszLastDot = '\0';
|
|
oIter = oLayerOCGMap.find(papszLayers[i]);
|
|
if (oIter != oLayerOCGMap.end())
|
|
{
|
|
if (oIter->second)
|
|
{
|
|
//CPLDebug("PDF", "Turn '%s' on too", papszLayers[i]);
|
|
oIter->second->setState(OptionalContentGroup::On);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
|
|
papszLayers[i]);
|
|
}
|
|
}
|
|
CSLDestroy(papszLayers);
|
|
|
|
bUseOCG = TRUE;
|
|
}
|
|
|
|
// Which layers to turn OFF ?
|
|
const char* pszLayersOFF = GetOption(papszOpenOptions, "LAYERS_OFF", NULL);
|
|
if (pszLayersOFF)
|
|
{
|
|
char** papszLayersOFF = CSLTokenizeString2(pszLayersOFF, ",", 0);
|
|
for(int i=0;papszLayersOFF[i] != NULL;i++)
|
|
{
|
|
std::map<CPLString, OptionalContentGroup*>::iterator oIter =
|
|
oLayerOCGMap.find(papszLayersOFF[i]);
|
|
if (oIter != oLayerOCGMap.end())
|
|
{
|
|
if (oIter->second)
|
|
{
|
|
//CPLDebug("PDF", "Turn '%s' off", papszLayersOFF[i]);
|
|
oIter->second->setState(OptionalContentGroup::Off);
|
|
}
|
|
|
|
// Turn child layers off too
|
|
size_t nLen = strlen(papszLayersOFF[i]);
|
|
oIter = oLayerOCGMap.begin();
|
|
for( ; oIter != oLayerOCGMap.end(); oIter ++)
|
|
{
|
|
if (oIter->first.size() > nLen &&
|
|
strncmp(oIter->first.c_str(), papszLayersOFF[i], nLen) == 0 &&
|
|
oIter->first[nLen] == '.')
|
|
{
|
|
if (oIter->second)
|
|
{
|
|
//CPLDebug("PDF", "Turn '%s' off too", oIter->first.c_str());
|
|
oIter->second->setState(OptionalContentGroup::Off);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
|
|
papszLayersOFF[i]);
|
|
}
|
|
}
|
|
CSLDestroy(papszLayersOFF);
|
|
|
|
bUseOCG = TRUE;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* FindLayerOCG() */
|
|
/************************************************************************/
|
|
|
|
CPLString PDFDataset::FindLayerOCG(GDALPDFDictionary* poPageDict,
|
|
const char* pszLayerName)
|
|
{
|
|
GDALPDFObject* poProperties =
|
|
poPageDict->LookupObject("Resources.Properties");
|
|
if (poProperties != NULL &&
|
|
poProperties->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
std::map<CPLString, GDALPDFObject*>& oMap =
|
|
poProperties->GetDictionary()->GetValues();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oIter = oMap.begin();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oEnd = oMap.end();
|
|
|
|
for(; oIter != oEnd; ++oIter)
|
|
{
|
|
GDALPDFObject* poObj = oIter->second;
|
|
if( poObj->GetRefNum() != 0 && poObj->GetType() == PDFObjectType_Dictionary )
|
|
{
|
|
GDALPDFObject* poType = poObj->GetDictionary()->Get("Type");
|
|
GDALPDFObject* poName = poObj->GetDictionary()->Get("Name");
|
|
if( poType != NULL &&
|
|
poType->GetType() == PDFObjectType_Name &&
|
|
poType->GetName() == "OCG" &&
|
|
poName != NULL &&
|
|
poName->GetType() == PDFObjectType_String )
|
|
{
|
|
if( strcmp(poName->GetString().c_str(), pszLayerName) == 0)
|
|
return oIter->first;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindLayersGeneric() */
|
|
/************************************************************************/
|
|
|
|
void PDFDataset::FindLayersGeneric(GDALPDFDictionary* poPageDict)
|
|
{
|
|
GDALPDFObject* poProperties =
|
|
poPageDict->LookupObject("Resources.Properties");
|
|
if (poProperties != NULL &&
|
|
poProperties->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
std::map<CPLString, GDALPDFObject*>& oMap =
|
|
poProperties->GetDictionary()->GetValues();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oIter = oMap.begin();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oEnd = oMap.end();
|
|
|
|
for(; oIter != oEnd; ++oIter)
|
|
{
|
|
GDALPDFObject* poObj = oIter->second;
|
|
if( poObj->GetRefNum() != 0 && poObj->GetType() == PDFObjectType_Dictionary )
|
|
{
|
|
GDALPDFObject* poType = poObj->GetDictionary()->Get("Type");
|
|
GDALPDFObject* poName = poObj->GetDictionary()->Get("Name");
|
|
if( poType != NULL &&
|
|
poType->GetType() == PDFObjectType_Name &&
|
|
poType->GetName() == "OCG" &&
|
|
poName != NULL &&
|
|
poName->GetType() == PDFObjectType_String )
|
|
{
|
|
osLayerWithRefList.AddString(
|
|
CPLSPrintf("%s %d %d",
|
|
PDFSanitizeLayerName(poName->GetString()).c_str(),
|
|
poObj->GetRefNum(),
|
|
poObj->GetRefGen()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *PDFDataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if (!Identify(poOpenInfo))
|
|
return NULL;
|
|
|
|
const char* pszUserPwd = GetOption(poOpenInfo->papszOpenOptions, "USER_PWD", NULL);
|
|
|
|
int bOpenSubdataset = strncmp(poOpenInfo->pszFilename, "PDF:", 4) == 0;
|
|
int bOpenSubdatasetImage = strncmp(poOpenInfo->pszFilename, "PDF_IMAGE:", 10) == 0;
|
|
int iPage = -1;
|
|
int nImageNum = -1;
|
|
const char* pszFilename = poOpenInfo->pszFilename;
|
|
char szPassword[81];
|
|
|
|
if (bOpenSubdataset)
|
|
{
|
|
iPage = atoi(pszFilename + 4);
|
|
if (iPage <= 0)
|
|
return NULL;
|
|
pszFilename = strchr(pszFilename + 4, ':');
|
|
if (pszFilename == NULL)
|
|
return NULL;
|
|
pszFilename ++;
|
|
}
|
|
else if (bOpenSubdatasetImage)
|
|
{
|
|
iPage = atoi(pszFilename + 10);
|
|
if (iPage <= 0)
|
|
return NULL;
|
|
const char* pszNext = strchr(pszFilename + 10, ':');
|
|
if (pszNext == NULL)
|
|
return NULL;
|
|
nImageNum = atoi(pszNext + 1);
|
|
if (nImageNum <= 0)
|
|
return NULL;
|
|
pszFilename = strchr(pszNext + 1, ':');
|
|
if (pszFilename == NULL)
|
|
return NULL;
|
|
pszFilename ++;
|
|
}
|
|
else
|
|
iPage = 1;
|
|
|
|
int bUsePoppler;
|
|
#if defined(HAVE_POPPLER) && !defined(HAVE_PODOFO)
|
|
bUsePoppler = TRUE;
|
|
#elif !defined(HAVE_POPPLER) && defined(HAVE_PODOFO)
|
|
bUsePoppler = FALSE;
|
|
#elif defined(HAVE_POPPLER) && defined(HAVE_PODOFO)
|
|
const char* pszPDFLib = GetOption(poOpenInfo->papszOpenOptions, "PDF_LIB", "POPPLER");
|
|
if (EQUAL(pszPDFLib, "POPPLER"))
|
|
bUsePoppler = TRUE;
|
|
else if (EQUAL(pszPDFLib, "PODOFO"))
|
|
bUsePoppler = FALSE;
|
|
else
|
|
{
|
|
CPLDebug("PDF", "Invalid value for GDAL_PDF_LIB config option");
|
|
bUsePoppler = TRUE;
|
|
}
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
|
|
GDALPDFObject* poPageObj = NULL;
|
|
#ifdef HAVE_POPPLER
|
|
PDFDoc* poDocPoppler = NULL;
|
|
ObjectAutoFree oObj;
|
|
Page* poPagePoppler = NULL;
|
|
Catalog* poCatalogPoppler = NULL;
|
|
#endif
|
|
#ifdef HAVE_PODOFO
|
|
PoDoFo::PdfMemDocument* poDocPodofo = NULL;
|
|
PoDoFo::PdfPage* poPagePodofo = NULL;
|
|
#endif
|
|
int nPages = 0;
|
|
|
|
#ifdef HAVE_POPPLER
|
|
if(bUsePoppler)
|
|
{
|
|
GooString* poUserPwd = NULL;
|
|
|
|
/* Set custom error handler for poppler errors */
|
|
#ifdef POPPLER_0_20_OR_LATER
|
|
setErrorCallback(PDFDatasetErrorFunction, NULL);
|
|
#else
|
|
setErrorFunction(PDFDatasetErrorFunction);
|
|
#endif
|
|
|
|
{
|
|
CPLMutexHolderD(&hGlobalParamsMutex);
|
|
/* poppler global variable */
|
|
if (globalParams == NULL)
|
|
globalParams = new GlobalParams();
|
|
|
|
globalParams->setPrintCommands(CSLTestBoolean(
|
|
CPLGetConfigOption("GDAL_PDF_PRINT_COMMANDS", "FALSE")));
|
|
}
|
|
|
|
while(TRUE)
|
|
{
|
|
VSILFILE* fp = VSIFOpenL(pszFilename, "rb");
|
|
if (fp == NULL)
|
|
return NULL;
|
|
|
|
fp = (VSILFILE*)VSICreateBufferedReaderHandle((VSIVirtualHandle*)fp);
|
|
|
|
if (pszUserPwd)
|
|
poUserPwd = new GooString(pszUserPwd);
|
|
|
|
oObj.initNull();
|
|
poDocPoppler = new PDFDoc(new VSIPDFFileStream(fp, pszFilename, &oObj), NULL, poUserPwd);
|
|
delete poUserPwd;
|
|
|
|
if ( !poDocPoppler->isOk() || poDocPoppler->getNumPages() == 0 )
|
|
{
|
|
if (poDocPoppler->getErrorCode() == errEncrypted)
|
|
{
|
|
if (pszUserPwd && EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
|
|
{
|
|
printf( "Enter password (will be echo'ed in the console): " );
|
|
if (0 == fgets( szPassword, sizeof(szPassword), stdin ))
|
|
{
|
|
fprintf(stderr, "WARNING: Error getting password.\n");
|
|
}
|
|
szPassword[sizeof(szPassword)-1] = 0;
|
|
char* sz10 = strchr(szPassword, '\n');
|
|
if (sz10)
|
|
*sz10 = 0;
|
|
pszUserPwd = szPassword;
|
|
PDFFreeDoc(poDocPoppler);
|
|
continue;
|
|
}
|
|
else if (pszUserPwd == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"A password is needed. You can specify it through the PDF_USER_PWD "
|
|
"configuration option (that can be set to ASK_INTERACTIVE)");
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid password");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
|
|
}
|
|
|
|
PDFFreeDoc(poDocPoppler);
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
break;
|
|
|
|
/* Reset errors that could have been issued during opening and that */
|
|
/* did not result in an invalid document */
|
|
CPLErrorReset();
|
|
}
|
|
|
|
poCatalogPoppler = poDocPoppler->getCatalog();
|
|
if ( poCatalogPoppler == NULL || !poCatalogPoppler->isOk() )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid catalog");
|
|
PDFFreeDoc(poDocPoppler);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef dump_catalog
|
|
{
|
|
ObjectAutoFree oCatalog;
|
|
poDocPoppler->getXRef()->getCatalog(&oCatalog);
|
|
GDALPDFObjectPoppler oCatalogGDAL(&oCatalog, FALSE);
|
|
GDALPDFDumper oDumper(stderr);
|
|
oDumper.Dump(&oCatalogGDAL);
|
|
}
|
|
#endif
|
|
|
|
nPages = poDocPoppler->getNumPages();
|
|
if (iPage < 1 || iPage > nPages)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
|
|
iPage, nPages);
|
|
PDFFreeDoc(poDocPoppler);
|
|
return NULL;
|
|
}
|
|
|
|
/* Sanity check to validate page count */
|
|
if( iPage != nPages )
|
|
{
|
|
poPagePoppler = poCatalogPoppler->getPage(nPages);
|
|
if ( poPagePoppler == NULL || !poPagePoppler->isOk() )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page count");
|
|
PDFFreeDoc(poDocPoppler);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
poPagePoppler = poCatalogPoppler->getPage(iPage);
|
|
if ( poPagePoppler == NULL || !poPagePoppler->isOk() )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
|
|
PDFFreeDoc(poDocPoppler);
|
|
return NULL;
|
|
}
|
|
|
|
/* Here's the dirty part: this is a private member */
|
|
/* so we had to #define private public to get it ! */
|
|
Object& oPageObj = poPagePoppler->pageObj;
|
|
if ( !oPageObj.isDict() )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : !oPageObj.isDict()");
|
|
PDFFreeDoc(poDocPoppler);
|
|
return NULL;
|
|
}
|
|
|
|
poPageObj = new GDALPDFObjectPoppler(&oPageObj, FALSE);
|
|
Ref* poPageRef = poCatalogPoppler->getPageRef(iPage);
|
|
if (poPageRef != NULL)
|
|
{
|
|
((GDALPDFObjectPoppler*)poPageObj)->SetRefNumAndGen(poPageRef->num, poPageRef->gen);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
{
|
|
PoDoFo::PdfError::EnableDebug( false );
|
|
PoDoFo::PdfError::EnableLogging( false );
|
|
|
|
poDocPodofo = new PoDoFo::PdfMemDocument();
|
|
try
|
|
{
|
|
poDocPodofo->Load(pszFilename);
|
|
}
|
|
catch(PoDoFo::PdfError& oError)
|
|
{
|
|
if (oError.GetError() == PoDoFo::ePdfError_InvalidPassword)
|
|
{
|
|
if (pszUserPwd)
|
|
{
|
|
if (EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
|
|
{
|
|
printf( "Enter password (will be echo'ed in the console): " );
|
|
if (0 == fgets( szPassword, sizeof(szPassword), stdin ))
|
|
{
|
|
fprintf(stderr, "WARNING: Error getting password.\n");
|
|
}
|
|
szPassword[sizeof(szPassword)-1] = 0;
|
|
char* sz10 = strchr(szPassword, '\n');
|
|
if (sz10)
|
|
*sz10 = 0;
|
|
pszUserPwd = szPassword;
|
|
}
|
|
|
|
try
|
|
{
|
|
poDocPodofo->SetPassword(pszUserPwd);
|
|
}
|
|
catch(PoDoFo::PdfError& oError)
|
|
{
|
|
if (oError.GetError() == PoDoFo::ePdfError_InvalidPassword)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid password");
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s", oError.what());
|
|
}
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
catch(...)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"A password is needed. You can specify it through the PDF_USER_PWD "
|
|
"configuration option (that can be set to ASK_INTERACTIVE)");
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s", oError.what());
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
|
|
nPages = poDocPodofo->GetPageCount();
|
|
if (iPage < 1 || iPage > nPages)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
|
|
iPage, nPages);
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
|
|
try
|
|
{
|
|
/* Sanity check to validate page count */
|
|
if( iPage != nPages )
|
|
poPagePodofo = poDocPodofo->GetPage(nPages - 1);
|
|
|
|
poPagePodofo = poDocPodofo->GetPage(iPage - 1);
|
|
}
|
|
catch(PoDoFo::PdfError& oError)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s", oError.what());
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
catch(...)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
|
|
if ( poPagePodofo == NULL )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
|
|
delete poDocPodofo;
|
|
return NULL;
|
|
}
|
|
|
|
PoDoFo::PdfObject* pObj = poPagePodofo->GetObject();
|
|
poPageObj = new GDALPDFObjectPodofo(pObj, poDocPodofo->GetObjects());
|
|
}
|
|
#endif
|
|
|
|
GDALPDFDictionary* poPageDict = poPageObj->GetDictionary();
|
|
if ( poPageDict == NULL )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : poPageDict == NULL");
|
|
#ifdef HAVE_POPPLER
|
|
PDFFreeDoc(poDocPoppler);
|
|
#endif
|
|
#ifdef HAVE_PODOFO
|
|
delete poDocPodofo;
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
const char* pszDumpObject = CPLGetConfigOption("PDF_DUMP_OBJECT", NULL);
|
|
if (pszDumpObject != NULL)
|
|
{
|
|
FILE* f;
|
|
if (strcmp(pszDumpObject, "stderr") == 0)
|
|
f = stderr;
|
|
else if (EQUAL(pszDumpObject, "YES"))
|
|
f = fopen(CPLSPrintf("dump_%s.txt", CPLGetFilename(pszFilename)), "wt");
|
|
else
|
|
f = fopen(pszDumpObject, "wt");
|
|
if (f == NULL)
|
|
f = stderr;
|
|
|
|
GDALPDFDumper oDumper(f);
|
|
oDumper.Dump(poPageObj);
|
|
if (f != stderr)
|
|
fclose(f);
|
|
}
|
|
|
|
PDFDataset* poDS = new PDFDataset();
|
|
poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
|
|
poDS->bUsePoppler = bUsePoppler;
|
|
poDS->osFilename = pszFilename;
|
|
poDS->eAccess = poOpenInfo->eAccess;
|
|
|
|
if ( nPages > 1 && !bOpenSubdataset )
|
|
{
|
|
int i;
|
|
CPLStringList aosList;
|
|
for(i=0;i<nPages;i++)
|
|
{
|
|
char szKey[32];
|
|
sprintf( szKey, "SUBDATASET_%d_NAME", i+1 );
|
|
aosList.AddNameValue(szKey, CPLSPrintf("PDF:%d:%s", i+1, poOpenInfo->pszFilename));
|
|
sprintf( szKey, "SUBDATASET_%d_DESC", i+1 );
|
|
aosList.AddNameValue(szKey, CPLSPrintf("Page %d of %s", i+1, poOpenInfo->pszFilename));
|
|
}
|
|
poDS->SetMetadata( aosList.List(), "SUBDATASETS" );
|
|
}
|
|
|
|
#ifdef HAVE_POPPLER
|
|
poDS->poDocPoppler = poDocPoppler;
|
|
#endif
|
|
#ifdef HAVE_PODOFO
|
|
poDS->poDocPodofo = poDocPodofo;
|
|
#endif
|
|
poDS->poPageObj = poPageObj;
|
|
poDS->osUserPwd = pszUserPwd ? pszUserPwd : "";
|
|
poDS->iPage = iPage;
|
|
|
|
int nBandsGuessed = 0;
|
|
if (nImageNum < 0)
|
|
{
|
|
poDS->GuessDPI(poPageDict, &nBandsGuessed);
|
|
if( nBandsGuessed < 4 )
|
|
nBandsGuessed = 0;
|
|
}
|
|
else
|
|
{
|
|
const char* pszDPI = GetOption(poOpenInfo->papszOpenOptions, "DPI", NULL);
|
|
if (pszDPI != NULL)
|
|
{
|
|
poDS->dfDPI = CPLAtof(pszDPI);
|
|
}
|
|
}
|
|
|
|
double dfX1 = 0.0, dfY1 = 0.0, dfX2 = 0.0, dfY2 = 0.0;
|
|
|
|
#ifdef HAVE_POPPLER
|
|
if (bUsePoppler)
|
|
{
|
|
PDFRectangle* psMediaBox = poPagePoppler->getMediaBox();
|
|
dfX1 = psMediaBox->x1;
|
|
dfY1 = psMediaBox->y1;
|
|
dfX2 = psMediaBox->x2;
|
|
dfY2 = psMediaBox->y2;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
{
|
|
PoDoFo::PdfRect oMediaBox = poPagePodofo->GetMediaBox();
|
|
dfX1 = oMediaBox.GetLeft();
|
|
dfY1 = oMediaBox.GetBottom();
|
|
dfX2 = dfX1 + oMediaBox.GetWidth();
|
|
dfY2 = dfY1 + oMediaBox.GetHeight();
|
|
}
|
|
#endif
|
|
|
|
double dfUserUnit = poDS->dfDPI * USER_UNIT_IN_INCH;
|
|
poDS->dfPageWidth = dfX2 - dfX1;
|
|
poDS->dfPageHeight = dfY2 - dfY1;
|
|
poDS->nRasterXSize = (int) floor((dfX2 - dfX1) * dfUserUnit+0.5);
|
|
poDS->nRasterYSize = (int) floor((dfY2 - dfY1) * dfUserUnit+0.5);
|
|
|
|
if( !GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
double dfRotation = 0;
|
|
#ifdef HAVE_POPPLER
|
|
if (bUsePoppler)
|
|
dfRotation = poDocPoppler->getPageRotate(iPage);
|
|
#endif
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
dfRotation = poPagePodofo->GetRotation();
|
|
#endif
|
|
if ( dfRotation == 90 ||
|
|
dfRotation == -90 ||
|
|
dfRotation == 270 )
|
|
{
|
|
/* FIXME: the non poppler case should be implemented. This needs to rotate */
|
|
/* the output of pdftoppm */
|
|
#ifdef HAVE_POPPLER
|
|
if (bUsePoppler)
|
|
{
|
|
/* Wondering how it would work with a georeferenced image */
|
|
/* Has only been tested with ungeoreferenced image */
|
|
int nTmp = poDS->nRasterXSize;
|
|
poDS->nRasterXSize = poDS->nRasterYSize;
|
|
poDS->nRasterYSize = nTmp;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Check if the PDF is only made of regularly tiled images */
|
|
/* (like some USGS GeoPDF production) */
|
|
if( dfRotation == 0.0 && poDS->asTiles.size() &&
|
|
EQUAL(GetOption(poOpenInfo->papszOpenOptions, "LAYERS", "ALL"), "ALL") )
|
|
{
|
|
poDS->CheckTiledRaster();
|
|
if (poDS->aiTiles.size() )
|
|
poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
|
|
}
|
|
|
|
GDALPDFObject* poLGIDict = NULL;
|
|
GDALPDFObject* poVP = NULL;
|
|
int bIsOGCBP = FALSE;
|
|
if ( (poLGIDict = poPageDict->Get("LGIDict")) != NULL && nImageNum < 0 )
|
|
{
|
|
/* Cf 08-139r3_GeoPDF_Encoding_Best_Practice_Version_2.2.pdf */
|
|
CPLDebug("PDF", "OGC Encoding Best Practice style detected");
|
|
if (poDS->ParseLGIDictObject(poLGIDict))
|
|
{
|
|
if (poDS->bHasCTM )
|
|
{
|
|
if ( dfRotation == 90 )
|
|
{
|
|
poDS->adfGeoTransform[0] = poDS->adfCTM[4];
|
|
poDS->adfGeoTransform[1] = poDS->adfCTM[2] / dfUserUnit;
|
|
poDS->adfGeoTransform[2] = poDS->adfCTM[0] / dfUserUnit;
|
|
poDS->adfGeoTransform[3] = poDS->adfCTM[5];
|
|
poDS->adfGeoTransform[4] = poDS->adfCTM[3] / dfUserUnit;
|
|
poDS->adfGeoTransform[5] = poDS->adfCTM[1] / dfUserUnit;
|
|
}
|
|
else if ( dfRotation == -90 || dfRotation == 270 )
|
|
{
|
|
poDS->adfGeoTransform[0] = poDS->adfCTM[4] + poDS->adfCTM[2] * poDS->dfPageHeight + poDS->adfCTM[0] * poDS->dfPageWidth;
|
|
poDS->adfGeoTransform[1] = -poDS->adfCTM[2] / dfUserUnit;
|
|
poDS->adfGeoTransform[2] = -poDS->adfCTM[0] / dfUserUnit;
|
|
poDS->adfGeoTransform[3] = poDS->adfCTM[5] + poDS->adfCTM[3] * poDS->dfPageHeight + poDS->adfCTM[1] * poDS->dfPageWidth;
|
|
poDS->adfGeoTransform[4] = -poDS->adfCTM[3] / dfUserUnit;
|
|
poDS->adfGeoTransform[5] = -poDS->adfCTM[1] / dfUserUnit;
|
|
}
|
|
else
|
|
{
|
|
poDS->adfGeoTransform[0] = poDS->adfCTM[4] + poDS->adfCTM[2] * poDS->dfPageHeight;
|
|
poDS->adfGeoTransform[1] = poDS->adfCTM[0] / dfUserUnit;
|
|
poDS->adfGeoTransform[2] = - poDS->adfCTM[2] / dfUserUnit;
|
|
poDS->adfGeoTransform[3] = poDS->adfCTM[5] + poDS->adfCTM[3] * poDS->dfPageHeight;
|
|
poDS->adfGeoTransform[4] = poDS->adfCTM[1] / dfUserUnit;
|
|
poDS->adfGeoTransform[5] = - poDS->adfCTM[3] / dfUserUnit;
|
|
}
|
|
|
|
poDS->bGeoTransformValid = TRUE;
|
|
}
|
|
|
|
bIsOGCBP = TRUE;
|
|
|
|
int i;
|
|
for(i=0;i<poDS->nGCPCount;i++)
|
|
{
|
|
if ( dfRotation == 90 )
|
|
{
|
|
double dfPixel = poDS->pasGCPList[i].dfGCPPixel * dfUserUnit;
|
|
double dfLine = poDS->pasGCPList[i].dfGCPLine * dfUserUnit;
|
|
poDS->pasGCPList[i].dfGCPPixel = dfLine;
|
|
poDS->pasGCPList[i].dfGCPLine = dfPixel;
|
|
}
|
|
else if ( dfRotation == -90 || dfRotation == 270 )
|
|
{
|
|
double dfPixel = poDS->pasGCPList[i].dfGCPPixel * dfUserUnit;
|
|
double dfLine = poDS->pasGCPList[i].dfGCPLine * dfUserUnit;
|
|
poDS->pasGCPList[i].dfGCPPixel = poDS->nRasterXSize - dfLine;
|
|
poDS->pasGCPList[i].dfGCPLine = poDS->nRasterYSize - dfPixel;
|
|
}
|
|
else
|
|
{
|
|
poDS->pasGCPList[i].dfGCPPixel = poDS->pasGCPList[i].dfGCPPixel * dfUserUnit;
|
|
poDS->pasGCPList[i].dfGCPLine = poDS->nRasterYSize - poDS->pasGCPList[i].dfGCPLine * dfUserUnit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( (poVP = poPageDict->Get("VP")) != NULL && nImageNum < 0 )
|
|
{
|
|
/* Cf adobe_supplement_iso32000.pdf */
|
|
CPLDebug("PDF", "Adobe ISO32000 style Geospatial PDF perhaps ?");
|
|
if (dfX1 != 0 || dfY1 != 0)
|
|
{
|
|
CPLDebug("PDF", "non null dfX1 or dfY1 values. untested case...");
|
|
}
|
|
poDS->ParseVP(poVP, dfX2 - dfX1, dfY2 - dfY1);
|
|
}
|
|
else
|
|
{
|
|
GDALPDFObject* poXObject = poPageDict->LookupObject("Resources.XObject");
|
|
|
|
if (poXObject != NULL &&
|
|
poXObject->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poXObjectDict = poXObject->GetDictionary();
|
|
std::map<CPLString, GDALPDFObject*>& oMap = poXObjectDict->GetValues();
|
|
std::map<CPLString, GDALPDFObject*>::iterator oMapIter = oMap.begin();
|
|
int nSubDataset = 0;
|
|
while(oMapIter != oMap.end())
|
|
{
|
|
GDALPDFObject* poObj = oMapIter->second;
|
|
if (poObj->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poDict = poObj->GetDictionary();
|
|
GDALPDFObject* poSubtype = NULL;
|
|
GDALPDFObject* poMeasure = NULL;
|
|
GDALPDFObject* poWidth = NULL;
|
|
GDALPDFObject* poHeight = NULL;
|
|
int nW = 0;
|
|
int nH = 0;
|
|
if ((poSubtype = poDict->Get("Subtype")) != NULL &&
|
|
poSubtype->GetType() == PDFObjectType_Name &&
|
|
poSubtype->GetName() == "Image" &&
|
|
(poMeasure = poDict->Get("Measure")) != NULL &&
|
|
poMeasure->GetType() == PDFObjectType_Dictionary &&
|
|
(poWidth = poDict->Get("Width")) != NULL &&
|
|
poWidth->GetType() == PDFObjectType_Int &&
|
|
(nW = poWidth->GetInt()) > 0 &&
|
|
(poHeight = poDict->Get("Height")) != NULL &&
|
|
poHeight->GetType() == PDFObjectType_Int &&
|
|
(nH = poHeight->GetInt()) > 0 )
|
|
{
|
|
if (nImageNum < 0)
|
|
CPLDebug("PDF", "Measure found on Image object (%d)",
|
|
poObj->GetRefNum());
|
|
|
|
GDALPDFObject* poColorSpace = poDict->Get("ColorSpace");
|
|
GDALPDFObject* poBitsPerComponent = poDict->Get("BitsPerComponent");
|
|
if (poObj->GetRefNum() != 0 &&
|
|
poObj->GetRefGen() == 0 &&
|
|
poColorSpace != NULL &&
|
|
poColorSpace->GetType() == PDFObjectType_Name &&
|
|
(poColorSpace->GetName() == "DeviceGray" ||
|
|
poColorSpace->GetName() == "DeviceRGB") &&
|
|
(poBitsPerComponent == NULL ||
|
|
(poBitsPerComponent->GetType() == PDFObjectType_Int &&
|
|
poBitsPerComponent->GetInt() == 8)))
|
|
{
|
|
if (nImageNum < 0)
|
|
{
|
|
nSubDataset ++;
|
|
poDS->SetMetadataItem(CPLSPrintf("SUBDATASET_%d_NAME",
|
|
nSubDataset),
|
|
CPLSPrintf("PDF_IMAGE:%d:%d:%s",
|
|
iPage, poObj->GetRefNum(), pszFilename),
|
|
"SUBDATASETS");
|
|
poDS->SetMetadataItem(CPLSPrintf("SUBDATASET_%d_DESC",
|
|
nSubDataset),
|
|
CPLSPrintf("Georeferenced image of size %dx%d of page %d of %s",
|
|
nW, nH, iPage, pszFilename),
|
|
"SUBDATASETS");
|
|
}
|
|
else if (poObj->GetRefNum() == nImageNum)
|
|
{
|
|
poDS->nRasterXSize = nW;
|
|
poDS->nRasterYSize = nH;
|
|
poDS->ParseMeasure(poMeasure, nW, nH, 0, nH, nW, 0);
|
|
poDS->poImageObj = poObj;
|
|
if (poColorSpace->GetName() == "DeviceGray")
|
|
nBandsGuessed = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++ oMapIter;
|
|
}
|
|
}
|
|
|
|
if (nImageNum >= 0 && poDS->poImageObj == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find image %d", nImageNum);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* Not a geospatial PDF doc */
|
|
}
|
|
|
|
/* If pixel size or top left coordinates are very close to an int, round them to the int */
|
|
double dfEps = ( fabs(poDS->adfGeoTransform[0]) > 1e5 &&
|
|
fabs(poDS->adfGeoTransform[3]) > 1e5 ) ? 1e-5 : 1e-8;
|
|
poDS->adfGeoTransform[0] = ROUND_TO_INT_IF_CLOSE(poDS->adfGeoTransform[0], dfEps);
|
|
poDS->adfGeoTransform[1] = ROUND_TO_INT_IF_CLOSE(poDS->adfGeoTransform[1]);
|
|
poDS->adfGeoTransform[3] = ROUND_TO_INT_IF_CLOSE(poDS->adfGeoTransform[3], dfEps);
|
|
poDS->adfGeoTransform[5] = ROUND_TO_INT_IF_CLOSE(poDS->adfGeoTransform[5]);
|
|
|
|
if (poDS->poNeatLine)
|
|
{
|
|
char* pszNeatLineWkt = NULL;
|
|
OGRLinearRing* poRing = poDS->poNeatLine->getExteriorRing();
|
|
/* Adobe style is already in target SRS units */
|
|
if (bIsOGCBP)
|
|
{
|
|
int nPoints = poRing->getNumPoints();
|
|
int i;
|
|
|
|
for(i=0;i<nPoints;i++)
|
|
{
|
|
double x, y;
|
|
if( dfRotation == 90.0 )
|
|
{
|
|
x = poRing->getY(i) * dfUserUnit;
|
|
y = poRing->getX(i) * dfUserUnit;
|
|
}
|
|
else if( dfRotation == -90.0 || dfRotation == 270.0 )
|
|
{
|
|
x = poDS->nRasterXSize - poRing->getY(i) * dfUserUnit;
|
|
y = poDS->nRasterYSize - poRing->getX(i) * dfUserUnit;
|
|
}
|
|
else
|
|
{
|
|
x = poRing->getX(i) * dfUserUnit;
|
|
y = poDS->nRasterYSize - poRing->getY(i) * dfUserUnit;
|
|
}
|
|
double X = poDS->adfGeoTransform[0] + x * poDS->adfGeoTransform[1] +
|
|
y * poDS->adfGeoTransform[2];
|
|
double Y = poDS->adfGeoTransform[3] + x * poDS->adfGeoTransform[4] +
|
|
y * poDS->adfGeoTransform[5];
|
|
poRing->setPoint(i, X, Y);
|
|
}
|
|
}
|
|
poRing->closeRings();
|
|
|
|
poDS->poNeatLine->exportToWkt(&pszNeatLineWkt);
|
|
if (nImageNum < 0)
|
|
poDS->SetMetadataItem("NEATLINE", pszNeatLineWkt);
|
|
CPLFree(pszNeatLineWkt);
|
|
}
|
|
|
|
|
|
#ifdef HAVE_POPPLER
|
|
if (bUsePoppler)
|
|
{
|
|
GooString* poMetadata = poCatalogPoppler->readMetadata();
|
|
if (poMetadata)
|
|
{
|
|
char* pszContent = poMetadata->getCString();
|
|
if (pszContent != NULL &&
|
|
strncmp(pszContent, "<?xpacket begin=", strlen("<?xpacket begin=")) == 0)
|
|
{
|
|
char *apszMDList[2];
|
|
apszMDList[0] = pszContent;
|
|
apszMDList[1] = NULL;
|
|
poDS->SetMetadata(apszMDList, "xml:XMP");
|
|
}
|
|
delete poMetadata;
|
|
}
|
|
|
|
/* Read Info object */
|
|
/* The test is necessary since with some corrupted PDFs poDocPoppler->getDocInfo() */
|
|
/* might abort() */
|
|
if( poDocPoppler->getXRef()->isOk() )
|
|
{
|
|
Object oInfo;
|
|
poDocPoppler->getDocInfo(&oInfo);
|
|
GDALPDFObjectPoppler oInfoObjPoppler(&oInfo, FALSE);
|
|
poDS->ParseInfo(&oInfoObjPoppler);
|
|
oInfo.free();
|
|
}
|
|
|
|
/* Find layers */
|
|
poDS->FindLayers();
|
|
|
|
/* Turn user specified layers on or off */
|
|
poDS->TurnLayersOnOff();
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler)
|
|
{
|
|
PoDoFo::TIVecObjects it = poDocPodofo->GetObjects().begin();
|
|
for( ; it != poDocPodofo->GetObjects().end(); ++it )
|
|
{
|
|
GDALPDFObjectPodofo oObjPodofo((*it), poDocPodofo->GetObjects());
|
|
poDS->FindXMP(&oObjPodofo);
|
|
}
|
|
|
|
/* Find layers */
|
|
poDS->FindLayersGeneric(poPageDict);
|
|
|
|
/* Read Info object */
|
|
PoDoFo::PdfInfo* poInfo = poDocPodofo->GetInfo();
|
|
if (poInfo != NULL)
|
|
{
|
|
GDALPDFObjectPodofo oInfoObjPodofo(poInfo->GetObject(), poDocPodofo->GetObjects());
|
|
poDS->ParseInfo(&oInfoObjPodofo);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int nBands = 3;
|
|
if( nBandsGuessed )
|
|
nBands = nBandsGuessed;
|
|
const char* pszPDFBands = GetOption(poOpenInfo->papszOpenOptions, "BANDS", NULL);
|
|
if( pszPDFBands )
|
|
{
|
|
nBands = atoi(pszPDFBands);
|
|
if (nBands != 3 && nBands != 4)
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Invalid value for GDAL_PDF_BANDS. Using 3 as a fallback");
|
|
nBands = 3;
|
|
}
|
|
}
|
|
#ifdef HAVE_PODOFO
|
|
if (!bUsePoppler && nBands == 4 && poDS->aiTiles.size() == 0)
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"GDAL_PDF_BANDS=4 only supported when PDF driver is compiled against Poppler. "
|
|
"Using 3 as a fallback");
|
|
nBands = 3;
|
|
}
|
|
#endif
|
|
|
|
int iBand;
|
|
for(iBand = 1; iBand <= nBands; iBand ++)
|
|
{
|
|
if (poDS->poImageObj != NULL)
|
|
poDS->SetBand(iBand, new PDFImageRasterBand(poDS, iBand));
|
|
else
|
|
poDS->SetBand(iBand, new PDFRasterBand(poDS, iBand));
|
|
}
|
|
|
|
int bHasNonEmptyVectorLayers = poDS->OpenVectorLayers(poPageDict);
|
|
|
|
/* Check if this is a raster-only PCIDSK file and that we are */
|
|
/* opened in vector-only mode */
|
|
if( (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
|
|
(poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
|
|
!bHasNonEmptyVectorLayers )
|
|
{
|
|
CPLDebug("PCIDSK", "This is a raster-only PDF dataset, "
|
|
"but it has been opened in vector-only mode");
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Support overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
|
|
|
|
/* Clear dirty flag */
|
|
poDS->bProjDirty = FALSE;
|
|
poDS->bNeatLineDirty = FALSE;
|
|
poDS->bInfoDirty = FALSE;
|
|
poDS->bXMPDirty = FALSE;
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseLGIDictObject() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseLGIDictObject(GDALPDFObject* poLGIDict)
|
|
{
|
|
int i;
|
|
int bOK = FALSE;
|
|
if (poLGIDict->GetType() == PDFObjectType_Array)
|
|
{
|
|
GDALPDFArray* poArray = poLGIDict->GetArray();
|
|
int nArrayLength = poArray->GetLength();
|
|
int iMax = -1;
|
|
GDALPDFObject* poArrayElt;
|
|
for (i=0; i<nArrayLength; i++)
|
|
{
|
|
if ( (poArrayElt = poArray->Get(i)) == NULL ||
|
|
poArrayElt->GetType() != PDFObjectType_Dictionary )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"LGIDict[%d] is not a dictionary", i);
|
|
return FALSE;
|
|
}
|
|
|
|
int bIsBestCandidate = FALSE;
|
|
if (ParseLGIDictDictFirstPass(poArrayElt->GetDictionary(), &bIsBestCandidate))
|
|
{
|
|
if (bIsBestCandidate || iMax < 0)
|
|
iMax = i;
|
|
}
|
|
}
|
|
|
|
if (iMax < 0)
|
|
return FALSE;
|
|
|
|
poArrayElt = poArray->Get(iMax);
|
|
bOK = ParseLGIDictDictSecondPass(poArrayElt->GetDictionary());
|
|
}
|
|
else if (poLGIDict->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
bOK = ParseLGIDictDictFirstPass(poLGIDict->GetDictionary()) &&
|
|
ParseLGIDictDictSecondPass(poLGIDict->GetDictionary());
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"LGIDict is of type %s", poLGIDict->GetTypeName());
|
|
}
|
|
|
|
return bOK;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Get() */
|
|
/************************************************************************/
|
|
|
|
static double Get(GDALPDFObject* poObj, int nIndice)
|
|
{
|
|
if (poObj->GetType() == PDFObjectType_Array && nIndice >= 0)
|
|
{
|
|
poObj = poObj->GetArray()->Get(nIndice);
|
|
if (poObj == NULL)
|
|
return 0;
|
|
return Get(poObj);
|
|
}
|
|
else if (poObj->GetType() == PDFObjectType_Int)
|
|
return poObj->GetInt();
|
|
else if (poObj->GetType() == PDFObjectType_Real)
|
|
return poObj->GetReal();
|
|
else if (poObj->GetType() == PDFObjectType_String)
|
|
{
|
|
const char* pszStr = poObj->GetString().c_str();
|
|
size_t nLen = strlen(pszStr);
|
|
/* cf Military_Installations_2008.pdf that has values like "96 0 0.0W" */
|
|
char chLast = pszStr[nLen-1];
|
|
if (chLast == 'W' || chLast == 'E' || chLast == 'N' || chLast == 'S')
|
|
{
|
|
double dfDeg = CPLAtof(pszStr);
|
|
double dfMin = 0, dfSec = 0;
|
|
const char* pszNext = strchr(pszStr, ' ');
|
|
if (pszNext)
|
|
pszNext ++;
|
|
if (pszNext)
|
|
dfMin = CPLAtof(pszNext);
|
|
if (pszNext)
|
|
pszNext = strchr(pszNext, ' ');
|
|
if (pszNext)
|
|
pszNext ++;
|
|
if (pszNext)
|
|
dfSec = CPLAtof(pszNext);
|
|
double dfVal = dfDeg + dfMin / 60 + dfSec / 3600;
|
|
if (chLast == 'W' || chLast == 'S')
|
|
return -dfVal;
|
|
else
|
|
return dfVal;
|
|
}
|
|
return CPLAtof(pszStr);
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined, "Unexpected type : %s",
|
|
poObj->GetTypeName());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Get() */
|
|
/************************************************************************/
|
|
|
|
static double Get(GDALPDFDictionary* poDict, const char* pszName)
|
|
{
|
|
GDALPDFObject* poObj;
|
|
if ( (poObj = poDict->Get(pszName)) != NULL )
|
|
return Get(poObj);
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find parameter %s", pszName);
|
|
return 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseLGIDictDictFirstPass() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseLGIDictDictFirstPass(GDALPDFDictionary* poLGIDict,
|
|
int* pbIsBestCandidate)
|
|
{
|
|
int i;
|
|
|
|
if (pbIsBestCandidate)
|
|
*pbIsBestCandidate = FALSE;
|
|
|
|
if (poLGIDict == NULL)
|
|
return FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Type attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poType;
|
|
if ((poType = poLGIDict->Get("Type")) == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Type of LGIDict object");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( poType->GetType() != PDFObjectType_Name )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid type for Type of LGIDict object");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( strcmp(poType->GetName().c_str(), "LGIDict") != 0 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid value for Type of LGIDict object : %s",
|
|
poType->GetName().c_str());
|
|
return FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Version attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poVersion;
|
|
if ((poVersion = poLGIDict->Get("Version")) == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Version of LGIDict object");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( poVersion->GetType() == PDFObjectType_String )
|
|
{
|
|
/* OGC best practice is 2.1 */
|
|
CPLDebug("PDF", "LGIDict Version : %s",
|
|
poVersion->GetString().c_str());
|
|
}
|
|
else if (poVersion->GetType() == PDFObjectType_Int)
|
|
{
|
|
/* Old TerraGo is 2 */
|
|
CPLDebug("PDF", "LGIDict Version : %d",
|
|
poVersion->GetInt());
|
|
}
|
|
|
|
/* USGS PDF maps have several LGIDict. Keep the one whose description */
|
|
/* is "Map Layers" by default */
|
|
const char* pszNeatlineToSelect =
|
|
GetOption(papszOpenOptions, "NEATLINE", "Map Layers");
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Neatline attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poNeatline;
|
|
if ((poNeatline = poLGIDict->Get("Neatline")) != NULL &&
|
|
poNeatline->GetType() == PDFObjectType_Array)
|
|
{
|
|
int nLength = poNeatline->GetArray()->GetLength();
|
|
if ( (nLength % 2) != 0 || nLength < 4 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for Neatline");
|
|
return FALSE;
|
|
}
|
|
|
|
GDALPDFObject* poDescription;
|
|
int bIsAskedNeatline = FALSE;
|
|
if ( (poDescription = poLGIDict->Get("Description")) != NULL &&
|
|
poDescription->GetType() == PDFObjectType_String )
|
|
{
|
|
CPLDebug("PDF", "Description = %s", poDescription->GetString().c_str());
|
|
|
|
if( EQUAL(poDescription->GetString().c_str(), pszNeatlineToSelect) )
|
|
{
|
|
dfMaxArea = 1e300;
|
|
bIsAskedNeatline = TRUE;
|
|
}
|
|
}
|
|
|
|
if( !bIsAskedNeatline )
|
|
{
|
|
double dfMinX = 0, dfMinY = 0, dfMaxX = 0, dfMaxY = 0;
|
|
for(i=0;i<nLength;i+=2)
|
|
{
|
|
double dfX = Get(poNeatline, i);
|
|
double dfY = Get(poNeatline, i + 1);
|
|
if (i == 0 || dfX < dfMinX) dfMinX = dfX;
|
|
if (i == 0 || dfY < dfMinY) dfMinY = dfY;
|
|
if (i == 0 || dfX > dfMaxX) dfMaxX = dfX;
|
|
if (i == 0 || dfY > dfMaxY) dfMaxY = dfY;
|
|
}
|
|
double dfArea = (dfMaxX - dfMinX) * (dfMaxY - dfMinY);
|
|
if (dfArea < dfMaxArea)
|
|
{
|
|
CPLDebug("PDF", "Not the largest neatline. Skipping it");
|
|
return TRUE;
|
|
}
|
|
|
|
CPLDebug("PDF", "This is the largest neatline for now");
|
|
dfMaxArea = dfArea;
|
|
}
|
|
else
|
|
CPLDebug("PDF", "The \"%s\" registration will be selected",
|
|
pszNeatlineToSelect);
|
|
|
|
if (pbIsBestCandidate)
|
|
*pbIsBestCandidate = TRUE;
|
|
|
|
delete poNeatLine;
|
|
poNeatLine = new OGRPolygon();
|
|
OGRLinearRing* poRing = new OGRLinearRing();
|
|
if (nLength == 4)
|
|
{
|
|
/* 2 points only ? They are the bounding box */
|
|
double dfX1 = Get(poNeatline, 0);
|
|
double dfY1 = Get(poNeatline, 1);
|
|
double dfX2 = Get(poNeatline, 2);
|
|
double dfY2 = Get(poNeatline, 3);
|
|
poRing->addPoint(dfX1, dfY1);
|
|
poRing->addPoint(dfX2, dfY1);
|
|
poRing->addPoint(dfX2, dfY2);
|
|
poRing->addPoint(dfX1, dfY2);
|
|
}
|
|
else
|
|
{
|
|
for(i=0;i<nLength;i+=2)
|
|
{
|
|
double dfX = Get(poNeatline, i);
|
|
double dfY = Get(poNeatline, i + 1);
|
|
poRing->addPoint(dfX, dfY);
|
|
}
|
|
}
|
|
poNeatLine->addRingDirectly(poRing);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseLGIDictDictSecondPass() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseLGIDictDictSecondPass(GDALPDFDictionary* poLGIDict)
|
|
{
|
|
int i;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Description attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poDescription;
|
|
if ( (poDescription = poLGIDict->Get("Description")) != NULL &&
|
|
poDescription->GetType() == PDFObjectType_String )
|
|
{
|
|
CPLDebug("PDF", "Description = %s", poDescription->GetString().c_str());
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract CTM attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poCTM;
|
|
bHasCTM = FALSE;
|
|
if ((poCTM = poLGIDict->Get("CTM")) != NULL &&
|
|
poCTM->GetType() == PDFObjectType_Array)
|
|
{
|
|
int nLength = poCTM->GetArray()->GetLength();
|
|
if ( nLength != 6 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for CTM");
|
|
return FALSE;
|
|
}
|
|
|
|
bHasCTM = TRUE;
|
|
for(i=0;i<nLength;i++)
|
|
{
|
|
adfCTM[i] = Get(poCTM, i);
|
|
/* Nullify rotation terms that are significantly smaller than */
|
|
/* scaling termes */
|
|
if ((i == 1 || i == 2) && fabs(adfCTM[i]) < fabs(adfCTM[0]) * 1e-10)
|
|
adfCTM[i] = 0;
|
|
CPLDebug("PDF", "CTM[%d] = %.16g", i, adfCTM[i]);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Registration attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poRegistration;
|
|
if ((poRegistration = poLGIDict->Get("Registration")) != NULL &&
|
|
poRegistration->GetType() == PDFObjectType_Array)
|
|
{
|
|
GDALPDFArray* poRegistrationArray = poRegistration->GetArray();
|
|
int nLength = poRegistrationArray->GetLength();
|
|
if( nLength > 4 || (!bHasCTM && nLength >= 2) ||
|
|
CSLTestBoolean(CPLGetConfigOption("PDF_REPORT_GCPS", "NO")) )
|
|
{
|
|
nGCPCount = 0;
|
|
pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nLength);
|
|
|
|
for(i=0;i<nLength;i++)
|
|
{
|
|
GDALPDFObject* poGCP = poRegistrationArray->Get(i);
|
|
if ( poGCP != NULL &&
|
|
poGCP->GetType() == PDFObjectType_Array &&
|
|
poGCP->GetArray()->GetLength() == 4 )
|
|
{
|
|
double dfUserX = Get(poGCP, 0);
|
|
double dfUserY = Get(poGCP, 1);
|
|
double dfX = Get(poGCP, 2);
|
|
double dfY = Get(poGCP, 3);
|
|
CPLDebug("PDF", "GCP[%d].userX = %.16g", i, dfUserX);
|
|
CPLDebug("PDF", "GCP[%d].userY = %.16g", i, dfUserY);
|
|
CPLDebug("PDF", "GCP[%d].x = %.16g", i, dfX);
|
|
CPLDebug("PDF", "GCP[%d].y = %.16g", i, dfY);
|
|
|
|
char szID[32];
|
|
sprintf( szID, "%d", nGCPCount+1 );
|
|
pasGCPList[nGCPCount].pszId = CPLStrdup( szID );
|
|
pasGCPList[nGCPCount].pszInfo = CPLStrdup("");
|
|
pasGCPList[nGCPCount].dfGCPPixel = dfUserX;
|
|
pasGCPList[nGCPCount].dfGCPLine = dfUserY;
|
|
pasGCPList[nGCPCount].dfGCPX = dfX;
|
|
pasGCPList[nGCPCount].dfGCPY = dfY;
|
|
nGCPCount ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bHasCTM && nGCPCount == 0)
|
|
{
|
|
CPLDebug("PDF", "Neither CTM nor Registration found");
|
|
return FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Projection attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poProjection;
|
|
if ((poProjection = poLGIDict->Get("Projection")) == NULL ||
|
|
poProjection->GetType() != PDFObjectType_Dictionary)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Could not find Projection");
|
|
return FALSE;
|
|
}
|
|
|
|
return ParseProjDict(poProjection->GetDictionary());
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseProjDict() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseProjDict(GDALPDFDictionary* poProjDict)
|
|
{
|
|
if (poProjDict == NULL)
|
|
return FALSE;
|
|
OGRSpatialReference oSRS;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract WKT attribute (GDAL extension) */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poWKT;
|
|
if ( (poWKT = poProjDict->Get("WKT")) != NULL &&
|
|
poWKT->GetType() == PDFObjectType_String &&
|
|
CSLTestBoolean( CPLGetConfigOption("GDAL_PDF_OGC_BP_READ_WKT", "TRUE") ) )
|
|
{
|
|
CPLDebug("PDF", "Found WKT attribute (GDAL extension). Using it");
|
|
const char* pszWKTRead = poWKT->GetString().c_str();
|
|
CPLFree(pszWKT);
|
|
pszWKT = CPLStrdup(pszWKTRead);
|
|
return TRUE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Type attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poType;
|
|
if ((poType = poProjDict->Get("Type")) == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Type of Projection object");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( poType->GetType() != PDFObjectType_Name )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid type for Type of Projection object");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( strcmp(poType->GetName().c_str(), "Projection") != 0 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid value for Type of Projection object : %s",
|
|
poType->GetName().c_str());
|
|
return FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Datum attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
int bIsWGS84 = FALSE;
|
|
int bIsNAD83 = FALSE;
|
|
/* int bIsNAD27 = FALSE; */
|
|
|
|
GDALPDFObject* poDatum;
|
|
if ((poDatum = poProjDict->Get("Datum")) != NULL)
|
|
{
|
|
if (poDatum->GetType() == PDFObjectType_String)
|
|
{
|
|
/* Using Annex A of http://portal.opengeospatial.org/files/?artifact_id=40537 */
|
|
const char* pszDatum = poDatum->GetString().c_str();
|
|
CPLDebug("PDF", "Datum = %s", pszDatum);
|
|
if (EQUAL(pszDatum, "WE") || EQUAL(pszDatum, "WGE"))
|
|
{
|
|
bIsWGS84 = TRUE;
|
|
oSRS.SetWellKnownGeogCS("WGS84");
|
|
}
|
|
else if (EQUAL(pszDatum, "NAR") || EQUALN(pszDatum, "NAR-", 4))
|
|
{
|
|
bIsNAD83 = TRUE;
|
|
oSRS.SetWellKnownGeogCS("NAD83");
|
|
}
|
|
else if (EQUAL(pszDatum, "NAS") || EQUALN(pszDatum, "NAS-", 4))
|
|
{
|
|
/* bIsNAD27 = TRUE; */
|
|
oSRS.SetWellKnownGeogCS("NAD27");
|
|
}
|
|
else if (EQUAL(pszDatum, "HEN")) /* HERAT North, Afghanistan */
|
|
{
|
|
oSRS.SetGeogCS( "unknown" /*const char * pszGeogName*/,
|
|
"unknown" /*const char * pszDatumName */,
|
|
"International 1924",
|
|
6378388,297);
|
|
oSRS.SetTOWGS84(-333,-222,114);
|
|
}
|
|
else if (EQUAL(pszDatum, "ING-A")) /* INDIAN 1960, Vietnam 16N */
|
|
{
|
|
oSRS.importFromEPSG(4131);
|
|
}
|
|
else if (EQUAL(pszDatum, "GDS")) /* Geocentric Datum of Australia */
|
|
{
|
|
oSRS.importFromEPSG(4283);
|
|
}
|
|
else if (EQUALN(pszDatum, "OHA-", 4)) /* Old Hawaiian */
|
|
{
|
|
oSRS.importFromEPSG(4135); /* matches OHA-M (Mean) */
|
|
if( !EQUAL(pszDatum, "OHA-M") )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Using OHA-M (Old Hawaiian Mean) definition for %s. Potential issue with datum shift parameters",
|
|
pszDatum);
|
|
OGR_SRSNode *poNode = oSRS.GetRoot();
|
|
int iChild = poNode->FindChild( "AUTHORITY" );
|
|
if( iChild != -1 )
|
|
poNode->DestroyChild( iChild );
|
|
iChild = poNode->FindChild( "DATUM" );
|
|
if( iChild != -1 )
|
|
{
|
|
poNode = poNode->GetChild(iChild);
|
|
iChild = poNode->FindChild( "AUTHORITY" );
|
|
if( iChild != -1 )
|
|
poNode->DestroyChild( iChild );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Unhandled (yet) value for Datum : %s. Defaulting to WGS84...",
|
|
pszDatum);
|
|
oSRS.SetGeogCS( "unknown" /*const char * pszGeogName*/,
|
|
"unknown" /*const char * pszDatumName */,
|
|
"unknown",
|
|
6378137,298.257223563);
|
|
}
|
|
}
|
|
else if (poDatum->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poDatumDict = poDatum->GetDictionary();
|
|
|
|
GDALPDFObject* poDatumDescription = poDatumDict->Get("Description");
|
|
const char* pszDatumDescription = "unknown";
|
|
if (poDatumDescription != NULL &&
|
|
poDatumDescription->GetType() == PDFObjectType_String)
|
|
pszDatumDescription = poDatumDescription->GetString().c_str();
|
|
CPLDebug("PDF", "Datum.Description = %s", pszDatumDescription);
|
|
|
|
GDALPDFObject* poEllipsoid = poDatumDict->Get("Ellipsoid");
|
|
if (poEllipsoid == NULL ||
|
|
!(poEllipsoid->GetType() == PDFObjectType_String ||
|
|
poEllipsoid->GetType() == PDFObjectType_Dictionary))
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Cannot find Ellipsoid in Datum. Defaulting to WGS84...");
|
|
oSRS.SetGeogCS( "unknown",
|
|
pszDatumDescription,
|
|
"unknown",
|
|
6378137,298.257223563);
|
|
}
|
|
else if (poEllipsoid->GetType() == PDFObjectType_String)
|
|
{
|
|
const char* pszEllipsoid = poEllipsoid->GetString().c_str();
|
|
CPLDebug("PDF", "Datum.Ellipsoid = %s", pszEllipsoid);
|
|
if( EQUAL(pszEllipsoid, "WE") )
|
|
{
|
|
oSRS.SetGeogCS( "unknown",
|
|
pszDatumDescription,
|
|
"WGS 84",
|
|
6378137,298.257223563);
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Unhandled (yet) value for Ellipsoid : %s. Defaulting to WGS84...",
|
|
pszEllipsoid);
|
|
oSRS.SetGeogCS( "unknown",
|
|
pszDatumDescription,
|
|
pszEllipsoid,
|
|
6378137,298.257223563);
|
|
}
|
|
}
|
|
else// if (poEllipsoid->GetType() == PDFObjectType_Dictionary)
|
|
{
|
|
GDALPDFDictionary* poEllipsoidDict = poEllipsoid->GetDictionary();
|
|
|
|
GDALPDFObject* poEllipsoidDescription = poEllipsoidDict->Get("Description");
|
|
const char* pszEllipsoidDescription = "unknown";
|
|
if (poEllipsoidDescription != NULL &&
|
|
poEllipsoidDescription->GetType() == PDFObjectType_String)
|
|
pszEllipsoidDescription = poEllipsoidDescription->GetString().c_str();
|
|
CPLDebug("PDF", "Datum.Ellipsoid.Description = %s", pszEllipsoidDescription);
|
|
|
|
double dfSemiMajor = Get(poEllipsoidDict, "SemiMajorAxis");
|
|
CPLDebug("PDF", "Datum.Ellipsoid.SemiMajorAxis = %.16g", dfSemiMajor);
|
|
double dfInvFlattening = -1.0;
|
|
|
|
if( poEllipsoidDict->Get("InvFlattening") )
|
|
{
|
|
dfInvFlattening = Get(poEllipsoidDict, "InvFlattening");
|
|
CPLDebug("PDF", "Datum.Ellipsoid.InvFlattening = %.16g", dfInvFlattening);
|
|
}
|
|
else if( poEllipsoidDict->Get("SemiMinorAxis") )
|
|
{
|
|
double dfSemiMinor = Get(poEllipsoidDict, "SemiMinorAxis");
|
|
CPLDebug("PDF", "Datum.Ellipsoid.SemiMinorAxis = %.16g", dfSemiMinor);
|
|
dfInvFlattening = OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor);
|
|
}
|
|
|
|
if( dfSemiMajor != 0.0 && dfInvFlattening != -1.0 )
|
|
{
|
|
oSRS.SetGeogCS( "unknown",
|
|
pszDatumDescription,
|
|
pszEllipsoidDescription,
|
|
dfSemiMajor, dfInvFlattening);
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Invalid Ellipsoid object. Defaulting to WGS84...");
|
|
oSRS.SetGeogCS( "unknown",
|
|
pszDatumDescription,
|
|
pszEllipsoidDescription,
|
|
6378137,298.257223563);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
GDALPDFObject* poTOWGS84 = poDatumDict->Get("ToWGS84");
|
|
if( poTOWGS84 != NULL && poTOWGS84->GetType() == PDFObjectType_Dictionary )
|
|
{
|
|
GDALPDFDictionary* poTOWGS84Dict = poTOWGS84->GetDictionary();
|
|
double dx = Get(poTOWGS84Dict, "dx");
|
|
double dy = Get(poTOWGS84Dict, "dy");
|
|
double dz = Get(poTOWGS84Dict, "dz");
|
|
if (poTOWGS84Dict->Get("rx") && poTOWGS84Dict->Get("ry") &&
|
|
poTOWGS84Dict->Get("rz") && poTOWGS84Dict->Get("sf"))
|
|
{
|
|
double rx = Get(poTOWGS84Dict, "rx");
|
|
double ry = Get(poTOWGS84Dict, "ry");
|
|
double rz = Get(poTOWGS84Dict, "rz");
|
|
double sf = Get(poTOWGS84Dict, "sf");
|
|
oSRS.SetTOWGS84(dx, dy, dz, rx, ry, rz, sf);
|
|
}
|
|
else
|
|
{
|
|
oSRS.SetTOWGS84(dx, dy, dz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Hemisphere attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString osHemisphere;
|
|
GDALPDFObject* poHemisphere;
|
|
if ((poHemisphere = poProjDict->Get("Hemisphere")) != NULL &&
|
|
poHemisphere->GetType() == PDFObjectType_String)
|
|
{
|
|
osHemisphere = poHemisphere->GetString();
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract ProjectionType attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poProjectionType;
|
|
if ((poProjectionType = poProjDict->Get("ProjectionType")) == NULL ||
|
|
poProjectionType->GetType() != PDFObjectType_String)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find ProjectionType of Projection object");
|
|
return FALSE;
|
|
}
|
|
CPLString osProjectionType(poProjectionType->GetString());
|
|
CPLDebug("PDF", "Projection.ProjectionType = %s", osProjectionType.c_str());
|
|
|
|
/* Unhandled: NONE, GEODETIC */
|
|
|
|
if (EQUAL(osProjectionType, "GEOGRAPHIC"))
|
|
{
|
|
/* Nothing to do */
|
|
}
|
|
|
|
/* Unhandled: LOCAL CARTESIAN, MG (MGRS) */
|
|
|
|
else if (EQUAL(osProjectionType, "UT")) /* UTM */
|
|
{
|
|
int nZone = (int)Get(poProjDict, "Zone");
|
|
int bNorth = EQUAL(osHemisphere, "N");
|
|
if (bIsWGS84)
|
|
oSRS.importFromEPSG( ((bNorth) ? 32600 : 32700) + nZone );
|
|
else
|
|
oSRS.SetUTM( nZone, bNorth );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "UP")) /* Universal Polar Stereographic (UPS) */
|
|
{
|
|
int bNorth = EQUAL(osHemisphere, "N");
|
|
if (bIsWGS84)
|
|
oSRS.importFromEPSG( (bNorth) ? 32661 : 32761 );
|
|
else
|
|
oSRS.SetPS( (bNorth) ? 90 : -90, 0,
|
|
0.994, 200000, 200000 );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "SPCS")) /* State Plane */
|
|
{
|
|
int nZone = (int)Get(poProjDict, "Zone");
|
|
oSRS.SetStatePlane( nZone, bIsNAD83 );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "AC")) /* Albers Equal Area Conic */
|
|
{
|
|
double dfStdP1 = Get(poProjDict, "StandardParallelOne");
|
|
double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetACEA( dfStdP1, dfStdP2,
|
|
dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "AL")) /* Azimuthal Equidistant */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetAE( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "BF")) /* Bonne */
|
|
{
|
|
double dfStdP1 = Get(poProjDict, "OriginLatitude");
|
|
double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetBonne( dfStdP1, dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "CS")) /* Cassini */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetCS( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "LI")) /* Cylindrical Equal Area */
|
|
{
|
|
double dfStdP1 = Get(poProjDict, "OriginLatitude");
|
|
double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetCEA( dfStdP1, dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "EF")) /* Eckert IV */
|
|
{
|
|
double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetEckertIV( dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "ED")) /* Eckert VI */
|
|
{
|
|
double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetEckertVI( dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "CP")) /* Equidistant Cylindrical */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "StandardParallel");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetEquirectangular( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "GN")) /* Gnomonic */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetGnomonic(dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "LE")) /* Lambert Conformal Conic */
|
|
{
|
|
double dfStdP1 = Get(poProjDict, "StandardParallelOne");
|
|
double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetLCC( dfStdP1, dfStdP2,
|
|
dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "MC")) /* Mercator */
|
|
{
|
|
#ifdef not_supported
|
|
if (poProjDict->Get("StandardParallelOne") == NULL)
|
|
{
|
|
#endif
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfScale = Get(poProjDict, "ScaleFactor");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetMercator( dfCenterLat, dfCenterLong,
|
|
dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
#ifdef not_supported
|
|
}
|
|
else
|
|
{
|
|
double dfStdP1 = Get(poProjDict, "StandardParallelOne");
|
|
double dfCenterLat = poProjDict->Get("OriginLatitude") ? Get(poProjDict, "OriginLatitude") : 0;
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetMercator2SP( dfStdP1,
|
|
dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "MH")) /* Miller Cylindrical */
|
|
{
|
|
double dfCenterLat = 0 /* ? */;
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetMC( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "MP")) /* Mollweide */
|
|
{
|
|
double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetMollweide( dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
/* Unhandled: "NY" : Ney's (Modified Lambert Conformal Conic) */
|
|
|
|
else if (EQUAL(osProjectionType, "NT")) /* New Zealand Map Grid */
|
|
{
|
|
/* No parameter specified in the PDF, so let's take the ones of EPSG:27200 */
|
|
double dfCenterLat = -41;
|
|
double dfCenterLong = 173;
|
|
double dfFalseEasting = 2510000;
|
|
double dfFalseNorthing = 6023150;
|
|
oSRS.SetNZMG( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "OC")) /* Oblique Mercator */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfLat1 = Get(poProjDict, "LatitudeOne");
|
|
double dfLong1 = Get(poProjDict, "LongitudeOne");
|
|
double dfLat2 = Get(poProjDict, "LatitudeTwo");
|
|
double dfLong2 = Get(poProjDict, "LongitudeTwo");
|
|
double dfScale = Get(poProjDict, "ScaleFactor");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetHOM2PNO( dfCenterLat,
|
|
dfLat1, dfLong1,
|
|
dfLat2, dfLong2,
|
|
dfScale,
|
|
dfFalseEasting,
|
|
dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "OD")) /* Orthographic */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetOrthographic( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "PG")) /* Polar Stereographic */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "LatitudeTrueScale");
|
|
double dfCenterLong = Get(poProjDict, "LongitudeDownFromPole");
|
|
double dfScale = 1.0;
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetPS( dfCenterLat, dfCenterLong,
|
|
dfScale,
|
|
dfFalseEasting, dfFalseNorthing);
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "PH")) /* Polyconic */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetPolyconic( dfCenterLat, dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "SA")) /* Sinusoidal */
|
|
{
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetSinusoidal( dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "SD")) /* Stereographic */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfScale = 1.0;
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetStereographic( dfCenterLat, dfCenterLong,
|
|
dfScale,
|
|
dfFalseEasting, dfFalseNorthing);
|
|
}
|
|
|
|
else if (EQUAL(osProjectionType, "TC")) /* Transverse Mercator */
|
|
{
|
|
double dfCenterLat = Get(poProjDict, "OriginLatitude");
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfScale = Get(poProjDict, "ScaleFactor");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
if (dfCenterLat == 0.0 && dfScale == 0.9996 && dfFalseEasting == 500000 &&
|
|
(dfFalseNorthing == 0.0 || dfFalseNorthing == 10000000.0))
|
|
{
|
|
int nZone = (int) floor( (dfCenterLong + 180.0) / 6.0 ) + 1;
|
|
int bNorth = dfFalseNorthing == 0;
|
|
if (bIsWGS84)
|
|
oSRS.importFromEPSG( ((bNorth) ? 32600 : 32700) + nZone );
|
|
else if (bIsNAD83 && bNorth)
|
|
oSRS.importFromEPSG( 26900 + nZone );
|
|
else
|
|
oSRS.SetUTM( nZone, bNorth );
|
|
}
|
|
else
|
|
{
|
|
oSRS.SetTM( dfCenterLat, dfCenterLong,
|
|
dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
}
|
|
|
|
/* Unhandled TX : Transverse Cylindrical Equal Area */
|
|
|
|
else if (EQUAL(osProjectionType, "VA")) /* Van der Grinten */
|
|
{
|
|
double dfCenterLong = Get(poProjDict, "CentralMeridian");
|
|
double dfFalseEasting = Get(poProjDict, "FalseEasting");
|
|
double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
|
|
oSRS.SetVDG( dfCenterLong,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unhandled (yet) value for ProjectionType : %s",
|
|
osProjectionType.c_str());
|
|
return FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Units attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString osUnits;
|
|
GDALPDFObject* poUnits;
|
|
if ((poUnits = poProjDict->Get("Units")) != NULL &&
|
|
poUnits->GetType() == PDFObjectType_String)
|
|
{
|
|
osUnits = poUnits->GetString();
|
|
CPLDebug("PDF", "Projection.Units = %s", osUnits.c_str());
|
|
|
|
if (EQUAL(osUnits, "M"))
|
|
oSRS.SetLinearUnits( "Meter", 1.0 );
|
|
else if (EQUAL(osUnits, "FT"))
|
|
oSRS.SetLinearUnits( "foot", 0.3048 );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Export SpatialRef */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
if (oSRS.exportToWkt(&pszWKT) != OGRERR_NONE)
|
|
{
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseVP() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseVP(GDALPDFObject* poVP, double dfMediaBoxWidth, double dfMediaBoxHeight)
|
|
{
|
|
int i;
|
|
|
|
if (poVP->GetType() != PDFObjectType_Array)
|
|
return FALSE;
|
|
|
|
GDALPDFArray* poVPArray = poVP->GetArray();
|
|
|
|
int nLength = poVPArray->GetLength();
|
|
CPLDebug("PDF", "VP length = %d", nLength);
|
|
if (nLength < 1)
|
|
return FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Find the largest BBox */
|
|
/* -------------------------------------------------------------------- */
|
|
int iLargest = 0;
|
|
double dfLargestArea = 0;
|
|
|
|
for(i=0;i<nLength;i++)
|
|
{
|
|
GDALPDFObject* poVPElt = poVPArray->Get(i);
|
|
if (poVPElt == NULL || poVPElt->GetType() != PDFObjectType_Dictionary)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
GDALPDFDictionary* poVPEltDict = poVPElt->GetDictionary();
|
|
|
|
GDALPDFObject* poBBox;
|
|
if( (poBBox = poVPEltDict->Get("BBox")) == NULL ||
|
|
poBBox->GetType() != PDFObjectType_Array )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Bbox object");
|
|
return FALSE;
|
|
}
|
|
|
|
int nBboxLength = poBBox->GetArray()->GetLength();
|
|
if (nBboxLength != 4)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for Bbox object");
|
|
return FALSE;
|
|
}
|
|
|
|
double adfBBox[4];
|
|
adfBBox[0] = Get(poBBox, 0);
|
|
adfBBox[1] = Get(poBBox, 1);
|
|
adfBBox[2] = Get(poBBox, 2);
|
|
adfBBox[3] = Get(poBBox, 3);
|
|
double dfArea = fabs(adfBBox[2] - adfBBox[0]) * fabs(adfBBox[3] - adfBBox[1]);
|
|
if (dfArea > dfLargestArea)
|
|
{
|
|
iLargest = i;
|
|
dfLargestArea = dfArea;
|
|
}
|
|
}
|
|
|
|
if (nLength > 1)
|
|
{
|
|
CPLDebug("PDF", "Largest BBox in VP array is element %d", iLargest);
|
|
}
|
|
|
|
|
|
GDALPDFObject* poVPElt = poVPArray->Get(iLargest);
|
|
if (poVPElt == NULL || poVPElt->GetType() != PDFObjectType_Dictionary)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
GDALPDFDictionary* poVPEltDict = poVPElt->GetDictionary();
|
|
|
|
GDALPDFObject* poBBox;
|
|
if( (poBBox = poVPEltDict->Get("BBox")) == NULL ||
|
|
poBBox->GetType() != PDFObjectType_Array )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Bbox object");
|
|
return FALSE;
|
|
}
|
|
|
|
int nBboxLength = poBBox->GetArray()->GetLength();
|
|
if (nBboxLength != 4)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for Bbox object");
|
|
return FALSE;
|
|
}
|
|
|
|
double dfULX = Get(poBBox, 0);
|
|
double dfULY = dfMediaBoxHeight - Get(poBBox, 1);
|
|
double dfLRX = Get(poBBox, 2);
|
|
double dfLRY = dfMediaBoxHeight - Get(poBBox, 3);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Measure attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poMeasure;
|
|
if( (poMeasure = poVPEltDict->Get("Measure")) == NULL ||
|
|
poMeasure->GetType() != PDFObjectType_Dictionary )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Measure object");
|
|
return FALSE;
|
|
}
|
|
|
|
int bRet = ParseMeasure(poMeasure, dfMediaBoxWidth, dfMediaBoxHeight,
|
|
dfULX, dfULY, dfLRX, dfLRY);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract PointData attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poPointData;
|
|
if( (poPointData = poVPEltDict->Get("PtData")) != NULL &&
|
|
poPointData->GetType() == PDFObjectType_Dictionary )
|
|
{
|
|
CPLDebug("PDF", "Found PointData");
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseMeasure() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::ParseMeasure(GDALPDFObject* poMeasure,
|
|
double dfMediaBoxWidth, double dfMediaBoxHeight,
|
|
double dfULX, double dfULY, double dfLRX, double dfLRY)
|
|
{
|
|
int i;
|
|
GDALPDFDictionary* poMeasureDict = poMeasure->GetDictionary();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Subtype attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poSubtype;
|
|
if( (poSubtype = poMeasureDict->Get("Subtype")) == NULL ||
|
|
poSubtype->GetType() != PDFObjectType_Name )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find Subtype object");
|
|
return FALSE;
|
|
}
|
|
|
|
CPLDebug("PDF", "Subtype = %s", poSubtype->GetName().c_str());
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract Bounds attribute (optional) */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* http://acrobatusers.com/sites/default/files/gallery_pictures/SEVERODVINSK.pdf */
|
|
/* has lgit:LPTS, lgit:GPTS and lgit:Bounds that have more precision than */
|
|
/* LPTS, GPTS and Bounds. Use those ones */
|
|
|
|
GDALPDFObject* poBounds;
|
|
if( (poBounds = poMeasureDict->Get("lgit:Bounds")) != NULL &&
|
|
poBounds->GetType() == PDFObjectType_Array )
|
|
{
|
|
CPLDebug("PDF", "Using lgit:Bounds");
|
|
}
|
|
else if( (poBounds = poMeasureDict->Get("Bounds")) == NULL ||
|
|
poBounds->GetType() != PDFObjectType_Array )
|
|
{
|
|
poBounds = NULL;
|
|
}
|
|
|
|
if (poBounds != NULL)
|
|
{
|
|
int nBoundsLength = poBounds->GetArray()->GetLength();
|
|
if (nBoundsLength == 8)
|
|
{
|
|
double adfBounds[8];
|
|
for(i=0;i<8;i++)
|
|
{
|
|
adfBounds[i] = Get(poBounds, i);
|
|
CPLDebug("PDF", "Bounds[%d] = %f", i, adfBounds[i]);
|
|
}
|
|
|
|
// TODO we should use it to restrict the neatline but
|
|
// I have yet to set a sample where bounds are not the four
|
|
// corners of the unit square.
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract GPTS attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poGPTS;
|
|
if( (poGPTS = poMeasureDict->Get("lgit:GPTS")) != NULL &&
|
|
poGPTS->GetType() == PDFObjectType_Array )
|
|
{
|
|
CPLDebug("PDF", "Using lgit:GPTS");
|
|
}
|
|
else if( (poGPTS = poMeasureDict->Get("GPTS")) == NULL ||
|
|
poGPTS->GetType() != PDFObjectType_Array )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find GPTS object");
|
|
return FALSE;
|
|
}
|
|
|
|
int nGPTSLength = poGPTS->GetArray()->GetLength();
|
|
if (nGPTSLength != 8)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for GPTS object");
|
|
return FALSE;
|
|
}
|
|
|
|
double adfGPTS[8];
|
|
for(i=0;i<8;i++)
|
|
{
|
|
adfGPTS[i] = Get(poGPTS, i);
|
|
CPLDebug("PDF", "GPTS[%d] = %.18f", i, adfGPTS[i]);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract LPTS attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poLPTS;
|
|
if( (poLPTS = poMeasureDict->Get("lgit:LPTS")) != NULL &&
|
|
poLPTS->GetType() == PDFObjectType_Array )
|
|
{
|
|
CPLDebug("PDF", "Using lgit:LPTS");
|
|
}
|
|
else if( (poLPTS = poMeasureDict->Get("LPTS")) == NULL ||
|
|
poLPTS->GetType() != PDFObjectType_Array )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find LPTS object");
|
|
return FALSE;
|
|
}
|
|
|
|
int nLPTSLength = poLPTS->GetArray()->GetLength();
|
|
if (nLPTSLength != 8)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid length for LPTS object");
|
|
return FALSE;
|
|
}
|
|
|
|
double adfLPTS[8];
|
|
for(i=0;i<8;i++)
|
|
{
|
|
adfLPTS[i] = Get(poLPTS, i);
|
|
CPLDebug("PDF", "LPTS[%d] = %f", i, adfLPTS[i]);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract GCS attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poGCS;
|
|
if( (poGCS = poMeasureDict->Get("GCS")) == NULL ||
|
|
poGCS->GetType() != PDFObjectType_Dictionary )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find GCS object");
|
|
return FALSE;
|
|
}
|
|
|
|
GDALPDFDictionary* poGCSDict = poGCS->GetDictionary();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract GCS.Type attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poGCSType;
|
|
if( (poGCSType = poGCSDict->Get("Type")) == NULL ||
|
|
poGCSType->GetType() != PDFObjectType_Name )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find GCS.Type object");
|
|
return FALSE;
|
|
}
|
|
|
|
CPLDebug("PDF", "GCS.Type = %s", poGCSType->GetName().c_str());
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract EPSG attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poEPSG;
|
|
int nEPSGCode = 0;
|
|
if( (poEPSG = poGCSDict->Get("EPSG")) != NULL &&
|
|
poEPSG->GetType() == PDFObjectType_Int )
|
|
{
|
|
nEPSGCode = poEPSG->GetInt();
|
|
CPLDebug("PDF", "GCS.EPSG = %d", nEPSGCode);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Extract GCS.WKT attribute */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALPDFObject* poGCSWKT = poGCSDict->Get("WKT");
|
|
if( poGCSWKT != NULL &&
|
|
poGCSWKT->GetType() != PDFObjectType_String )
|
|
{
|
|
poGCSWKT = NULL;
|
|
}
|
|
|
|
if (poGCSWKT != NULL)
|
|
CPLDebug("PDF", "GCS.WKT = %s", poGCSWKT->GetString().c_str());
|
|
|
|
if (nEPSGCode <= 0 && poGCSWKT == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot find GCS.WKT or GCS.EPSG objects");
|
|
return FALSE;
|
|
}
|
|
|
|
OGRSpatialReference oSRS;
|
|
int bSRSOK = FALSE;
|
|
if (nEPSGCode != 0 &&
|
|
oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
|
|
{
|
|
bSRSOK = TRUE;
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
oSRS.exportToWkt(&pszWKT);
|
|
}
|
|
else
|
|
{
|
|
if (poGCSWKT == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot resolve EPSG object, and GCS.WKT not found");
|
|
return FALSE;
|
|
}
|
|
|
|
CPLFree(pszWKT);
|
|
pszWKT = CPLStrdup(poGCSWKT->GetString().c_str());
|
|
}
|
|
|
|
if (!bSRSOK)
|
|
{
|
|
char* pszWktTemp = pszWKT;
|
|
if (oSRS.importFromWkt(&pszWktTemp) != OGRERR_NONE)
|
|
{
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* For http://www.avenza.com/sites/default/files/spatialpdf/US_County_Populations.pdf */
|
|
/* or http://www.agmkt.state.ny.us/soilwater/aem/gis_mapping_tools/HUC12_Albany.pdf */
|
|
const char* pszDatum = oSRS.GetAttrValue("Datum");
|
|
if (pszDatum && strncmp(pszDatum, "D_", 2) == 0)
|
|
{
|
|
oSRS.morphFromESRI();
|
|
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
if (oSRS.exportToWkt(&pszWKT) != OGRERR_NONE)
|
|
{
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("PDF", "WKT after morphFromESRI() = %s", pszWKT);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute geotransform */
|
|
/* -------------------------------------------------------------------- */
|
|
OGRSpatialReference* poSRSGeog = oSRS.CloneGeogCS();
|
|
|
|
/* Files found at http://carto.iict.ch/blog/publications-cartographiques-au-format-geospatial-pdf/ */
|
|
/* are in a PROJCS. However the coordinates in GPTS array are not in (lat, long) as required by the */
|
|
/* ISO 32000 supplement spec, but in (northing, easting). Adobe reader is able to understand that, */
|
|
/* so let's also try to do it with a heuristics. */
|
|
|
|
int bReproject = TRUE;
|
|
if (oSRS.IsProjected() &&
|
|
(fabs(adfGPTS[0]) > 91 || fabs(adfGPTS[2]) > 91 || fabs(adfGPTS[4]) > 91 || fabs(adfGPTS[6]) > 91 ||
|
|
fabs(adfGPTS[1]) > 361 || fabs(adfGPTS[3]) > 361 || fabs(adfGPTS[5]) > 361 || fabs(adfGPTS[7]) > 361))
|
|
{
|
|
CPLDebug("PDF", "GPTS coordinates seems to be in (northing, easting), which is non-standard");
|
|
bReproject = FALSE;
|
|
}
|
|
|
|
OGRCoordinateTransformation* poCT = NULL;
|
|
if (bReproject)
|
|
{
|
|
poCT = OGRCreateCoordinateTransformation( poSRSGeog, &oSRS);
|
|
if (poCT == NULL)
|
|
{
|
|
delete poSRSGeog;
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
GDAL_GCP asGCPS[4];
|
|
|
|
/* Create NEATLINE */
|
|
poNeatLine = new OGRPolygon();
|
|
OGRLinearRing* poRing = new OGRLinearRing();
|
|
poNeatLine->addRingDirectly(poRing);
|
|
|
|
for(i=0;i<4;i++)
|
|
{
|
|
/* We probably assume LPTS is 0 or 1 */
|
|
asGCPS[i].dfGCPPixel = (dfULX * (1 - adfLPTS[2*i+0]) + dfLRX * adfLPTS[2*i+0]) / dfMediaBoxWidth * nRasterXSize;
|
|
asGCPS[i].dfGCPLine = (dfULY * (1 - adfLPTS[2*i+1]) + dfLRY * adfLPTS[2*i+1]) / dfMediaBoxHeight * nRasterYSize;
|
|
|
|
double lat = adfGPTS[2*i], lon = adfGPTS[2*i+1];
|
|
double x = lon, y = lat;
|
|
if (bReproject)
|
|
{
|
|
if (!poCT->Transform(1, &x, &y, NULL))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot reproject (%f, %f)", lon, lat);
|
|
delete poSRSGeog;
|
|
delete poCT;
|
|
CPLFree(pszWKT);
|
|
pszWKT = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
x = ROUND_TO_INT_IF_CLOSE(x);
|
|
y = ROUND_TO_INT_IF_CLOSE(y);
|
|
|
|
asGCPS[i].dfGCPX = x;
|
|
asGCPS[i].dfGCPY = y;
|
|
|
|
poRing->addPoint(x, y);
|
|
}
|
|
|
|
delete poSRSGeog;
|
|
delete poCT;
|
|
|
|
if (!GDALGCPsToGeoTransform( 4, asGCPS,
|
|
adfGeoTransform, FALSE))
|
|
{
|
|
CPLDebug("PDF", "Could not compute GT with exact match. Try with approximate");
|
|
if (!GDALGCPsToGeoTransform( 4, asGCPS,
|
|
adfGeoTransform, TRUE))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Could not compute GT with approximate match.");
|
|
return FALSE;
|
|
}
|
|
}
|
|
bGeoTransformValid = TRUE;
|
|
|
|
/* If the non scaling terms of the geotransform are significantly smaller than */
|
|
/* the pixel size, then nullify them as being just artifacts of reprojection and */
|
|
/* GDALGCPsToGeoTransform() numerical imprecisions */
|
|
double dfPixelSize = MIN(fabs(adfGeoTransform[1]), fabs(adfGeoTransform[5]));
|
|
double dfRotationShearTerm = MAX(fabs(adfGeoTransform[2]), fabs(adfGeoTransform[4]));
|
|
if (dfRotationShearTerm < 1e-5 * dfPixelSize)
|
|
{
|
|
double dfLRX = adfGeoTransform[0] + nRasterXSize * adfGeoTransform[1] + nRasterYSize * adfGeoTransform[2];
|
|
double dfLRY = adfGeoTransform[3] + nRasterXSize * adfGeoTransform[4] + nRasterYSize * adfGeoTransform[5];
|
|
adfGeoTransform[1] = (dfLRX - adfGeoTransform[0]) / nRasterXSize;
|
|
adfGeoTransform[5] = (dfLRY - adfGeoTransform[3]) / nRasterYSize;
|
|
adfGeoTransform[2] = adfGeoTransform[4] = 0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetProjectionRef() */
|
|
/************************************************************************/
|
|
|
|
const char* PDFDataset::GetProjectionRef()
|
|
{
|
|
if (pszWKT && bGeoTransformValid)
|
|
return pszWKT;
|
|
return "";
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::GetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
memcpy(padfTransform, adfGeoTransform, 6 * sizeof(double));
|
|
|
|
return( (bGeoTransformValid) ? CE_None : CE_Failure );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetProjection() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::SetProjection(const char* pszWKTIn)
|
|
{
|
|
CPLFree(pszWKT);
|
|
pszWKT = pszWKTIn ? CPLStrdup(pszWKTIn) : CPLStrdup("");
|
|
bProjDirty = TRUE;
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::SetGeoTransform(double* padfGeoTransform)
|
|
{
|
|
memcpy(adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
|
|
bGeoTransformValid = TRUE;
|
|
bProjDirty = TRUE;
|
|
|
|
/* Reset NEATLINE if not explicitly set by the user */
|
|
if (!bNeatLineDirty)
|
|
SetMetadataItem("NEATLINE", NULL);
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetMetadataDomainList() */
|
|
/************************************************************************/
|
|
|
|
char **PDFDataset::GetMetadataDomainList()
|
|
{
|
|
return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
|
|
TRUE,
|
|
"xml:XMP", "LAYERS", "EMBEDDED_METADATA", NULL);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetMetadata() */
|
|
/************************************************************************/
|
|
|
|
char **PDFDataset::GetMetadata( const char * pszDomain )
|
|
{
|
|
if( pszDomain != NULL && EQUAL(pszDomain, "EMBEDDED_METADATA") )
|
|
{
|
|
char** papszRet = oMDMD.GetMetadata(pszDomain);
|
|
if( papszRet )
|
|
return papszRet;
|
|
|
|
GDALPDFObject* poCatalog = GetCatalog();
|
|
if( poCatalog == NULL )
|
|
return NULL;
|
|
GDALPDFObject* poFirstElt = poCatalog->LookupObject("Names.EmbeddedFiles.Names[0]");
|
|
GDALPDFObject* poF = poCatalog->LookupObject("Names.EmbeddedFiles.Names[1].EF.F");
|
|
|
|
if( poFirstElt == NULL || poFirstElt->GetType() != PDFObjectType_String ||
|
|
poFirstElt->GetString() != "Metadata" )
|
|
return NULL;
|
|
if( poF == NULL || poF->GetType() != PDFObjectType_Dictionary )
|
|
return NULL;
|
|
GDALPDFStream* poStream = poF->GetStream();
|
|
if( poStream == NULL )
|
|
return NULL;
|
|
|
|
char* apszMetadata[2] = { NULL, NULL };
|
|
apszMetadata[0] = poStream->GetBytes();
|
|
oMDMD.SetMetadata(apszMetadata, pszDomain);
|
|
VSIFree(apszMetadata[0]);
|
|
}
|
|
|
|
return oMDMD.GetMetadata(pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetMetadata() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::SetMetadata( char ** papszMetadata,
|
|
const char * pszDomain )
|
|
{
|
|
if (pszDomain == NULL || EQUAL(pszDomain, ""))
|
|
{
|
|
if (CSLFindString(papszMetadata, "NEATLINE") != -1)
|
|
{
|
|
bProjDirty = TRUE;
|
|
bNeatLineDirty = TRUE;
|
|
}
|
|
bInfoDirty = TRUE;
|
|
}
|
|
else if (EQUAL(pszDomain, "xml:XMP"))
|
|
bXMPDirty = TRUE;
|
|
return oMDMD.SetMetadata(papszMetadata, pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetMetadataItem() */
|
|
/************************************************************************/
|
|
|
|
const char *PDFDataset::GetMetadataItem( const char * pszName,
|
|
const char * pszDomain )
|
|
{
|
|
return oMDMD.GetMetadataItem(pszName, pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetMetadataItem() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::SetMetadataItem( const char * pszName,
|
|
const char * pszValue,
|
|
const char * pszDomain )
|
|
{
|
|
if (pszDomain == NULL || EQUAL(pszDomain, ""))
|
|
{
|
|
if (EQUAL(pszName, "NEATLINE"))
|
|
{
|
|
bProjDirty = TRUE;
|
|
bNeatLineDirty = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (pszValue == NULL)
|
|
pszValue = "";
|
|
bInfoDirty = TRUE;
|
|
}
|
|
}
|
|
else if (EQUAL(pszDomain, "xml:XMP"))
|
|
bXMPDirty = TRUE;
|
|
return oMDMD.SetMetadataItem(pszName, pszValue, pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGCPCount() */
|
|
/************************************************************************/
|
|
|
|
int PDFDataset::GetGCPCount()
|
|
{
|
|
return nGCPCount;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGCPProjection() */
|
|
/************************************************************************/
|
|
|
|
const char * PDFDataset::GetGCPProjection()
|
|
{
|
|
if (pszWKT != NULL && nGCPCount != 0)
|
|
return pszWKT;
|
|
return "";
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGCPs() */
|
|
/************************************************************************/
|
|
|
|
const GDAL_GCP * PDFDataset::GetGCPs()
|
|
{
|
|
return pasGCPList;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGCPs() */
|
|
/************************************************************************/
|
|
|
|
CPLErr PDFDataset::SetGCPs( int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
|
|
const char *pszGCPProjectionIn )
|
|
{
|
|
const char* pszGEO_ENCODING =
|
|
CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
|
|
if( nGCPCountIn != 4 && EQUAL(pszGEO_ENCODING, "ISO32000"))
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"PDF driver only supports writing 4 GCPs when "
|
|
"GDAL_PDF_GEO_ENCODING=ISO32000.");
|
|
return CE_Failure;
|
|
}
|
|
|
|
/* Free previous GCPs */
|
|
GDALDeinitGCPs( nGCPCount, pasGCPList );
|
|
CPLFree( pasGCPList );
|
|
|
|
/* Duplicate in GCPs */
|
|
nGCPCount = nGCPCountIn;
|
|
pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);
|
|
|
|
CPLFree(pszWKT);
|
|
pszWKT = CPLStrdup(pszGCPProjectionIn);
|
|
|
|
bProjDirty = TRUE;
|
|
|
|
/* Reset NEATLINE if not explicitly set by the user */
|
|
if (!bNeatLineDirty)
|
|
SetMetadataItem("NEATLINE", NULL);
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
#endif // #if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
|
|
|
|
/************************************************************************/
|
|
/* GDALPDFOpen() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset* GDALPDFOpen(
|
|
#if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
|
|
const char* pszFilename,
|
|
GDALAccess eAccess
|
|
#else
|
|
CPL_UNUSED const char* pszFilename,
|
|
CPL_UNUSED GDALAccess eAccess
|
|
#endif
|
|
)
|
|
{
|
|
#if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
|
|
GDALOpenInfo oOpenInfo(pszFilename, eAccess);
|
|
return PDFDataset::Open(&oOpenInfo);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALPDFUnloadDriver() */
|
|
/************************************************************************/
|
|
|
|
static void GDALPDFUnloadDriver(CPL_UNUSED GDALDriver * poDriver)
|
|
{
|
|
#ifdef HAVE_POPPLER
|
|
if( hGlobalParamsMutex != NULL )
|
|
CPLDestroyMutex(hGlobalParamsMutex);
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PDFSanitizeLayerName() */
|
|
/************************************************************************/
|
|
|
|
CPLString PDFSanitizeLayerName(const char* pszName)
|
|
{
|
|
CPLString osName;
|
|
for(int i=0; pszName[i] != '\0'; i++)
|
|
{
|
|
if (pszName[i] == ' ' || pszName[i] == '.' || pszName[i] == ',')
|
|
osName += "_";
|
|
else if (pszName[i] != '"')
|
|
osName += pszName[i];
|
|
}
|
|
return osName;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_PDF() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_PDF()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if (! GDAL_CHECK_VERSION("PDF driver"))
|
|
return;
|
|
|
|
if( GDALGetDriverByName( "PDF" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "PDF" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"Geospatial PDF" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_pdf.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "pdf" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
|
|
"Byte" );
|
|
#ifdef HAVE_POPPLER
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
poDriver->SetMetadataItem( "HAVE_POPPLER", "YES" );
|
|
#endif // HAVE_POPPLER
|
|
#ifdef HAVE_PODOFO
|
|
poDriver->SetMetadataItem( "HAVE_PODOFO", "YES" );
|
|
#endif // HAVE_PODOFO
|
|
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
|
|
"<CreationOptionList>\n"
|
|
" <Option name='COMPRESS' type='string-select' description='Compression method for raster data' default='DEFLATE'>\n"
|
|
" <Value>NONE</Value>\n"
|
|
" <Value>DEFLATE</Value>\n"
|
|
" <Value>JPEG</Value>\n"
|
|
" <Value>JPEG2000</Value>\n"
|
|
" </Option>\n"
|
|
" <Option name='STREAM_COMPRESS' type='string-select' description='Compression method for stream objects' default='DEFLATE'>\n"
|
|
" <Value>NONE</Value>\n"
|
|
" <Value>DEFLATE</Value>\n"
|
|
" </Option>\n"
|
|
" <Option name='GEO_ENCODING' type='string-select' description='Format of geo-encoding' default='ISO32000'>\n"
|
|
" <Value>NONE</Value>\n"
|
|
" <Value>ISO32000</Value>\n"
|
|
" <Value>OGC_BP</Value>\n"
|
|
" <Value>BOTH</Value>\n"
|
|
" </Option>\n"
|
|
" <Option name='NEATLINE' type='string' description='Neatline'/>\n"
|
|
" <Option name='DPI' type='float' description='DPI' default='72'/>\n"
|
|
" <Option name='PREDICTOR' type='int' description='Predictor Type (for DEFLATE compression)'/>\n"
|
|
" <Option name='JPEG_QUALITY' type='int' description='JPEG quality 1-100' default='75'/>\n"
|
|
" <Option name='JPEG2000_DRIVER' type='string'/>\n"
|
|
" <Option name='TILED' type='boolean' description='Switch to tiled format' default='NO'/>\n"
|
|
" <Option name='BLOCKXSIZE' type='int' description='Block Width'/>\n"
|
|
" <Option name='BLOCKYSIZE' type='int' description='Block Height'/>\n"
|
|
" <Option name='LAYER_NAME' type='string' description='Layer name for raster content'/>\n"
|
|
" <Option name='CLIPPING_EXTENT' type='string' description='Clipping extent for main and extra rasters. Format: xmin,ymin,xmax,ymax'/>\n"
|
|
" <Option name='EXTRA_RASTERS' type='string' description='List of extra (georeferenced) rasters.'/>\n"
|
|
" <Option name='EXTRA_RASTERS_LAYER_NAME' type='string' description='List of layer names for the extra (georeferenced) rasters.'/>\n"
|
|
" <Option name='EXTRA_STREAM' type='string' description='Extra data to insert into the page content stream'/>\n"
|
|
" <Option name='EXTRA_IMAGES' type='string' description='List of image_file_name,x,y,scale[,link=some_url] (possibly repeated)'/>\n"
|
|
" <Option name='EXTRA_LAYER_NAME' type='string' description='Layer name for extra content'/>\n"
|
|
" <Option name='MARGIN' type='int' description='Margin around image in user units'/>\n"
|
|
" <Option name='LEFT_MARGIN' type='int' description='Left margin in user units'/>\n"
|
|
" <Option name='RIGHT_MARGIN' type='int' description='Right margin in user units'/>\n"
|
|
" <Option name='TOP_MARGIN' type='int' description='Top margin in user units'/>\n"
|
|
" <Option name='BOTTOM_MARGIN' type='int' description='Bottom margin in user units'/>\n"
|
|
" <Option name='OGR_DATASOURCE' type='string' description='Name of OGR datasource to display on top of the raster layer'/>\n"
|
|
" <Option name='OGR_DISPLAY_FIELD' type='string' description='Name of field to use as the display field in the feature tree'/>\n"
|
|
" <Option name='OGR_DISPLAY_LAYER_NAMES' type='string' description='Comma separated list of OGR layer names to display in the feature tree'/>\n"
|
|
" <Option name='OGR_WRITE_ATTRIBUTES' type='boolean' description='Whether to write attributes of OGR features' default='YES'/>\n"
|
|
" <Option name='OGR_LINK_FIELD' type='string' description='Name of field to use as the URL field to make objects clickable.'/>\n"
|
|
" <Option name='XMP' type='string' description='xml:XMP metadata'/>\n"
|
|
" <Option name='WRITE_INFO' type='boolean' description='to control whether a Info block must be written' default='YES'/>\n"
|
|
" <Option name='AUTHOR' type='string'/>\n"
|
|
" <Option name='CREATOR' type='string'/>\n"
|
|
" <Option name='CREATION_DATE' type='string'/>\n"
|
|
" <Option name='KEYWORDS' type='string'/>\n"
|
|
" <Option name='PRODUCER' type='string'/>\n"
|
|
" <Option name='SUBJECT' type='string'/>\n"
|
|
" <Option name='TITLE' type='string'/>\n"
|
|
" <Option name='OFF_LAYERS' type='string' description='Comma separated list of layer names that should be initially hidden'/>\n"
|
|
" <Option name='EXCLUSIVE_LAYERS' type='string' description='Comma separated list of layer names, such that only one of those layers can be ON at a time.'/>\n"
|
|
" <Option name='JAVASCRIPT' type='string' description='Javascript script to embed and run at file opening'/>\n"
|
|
" <Option name='JAVASCRIPT_FILE' type='string' description='Filename of the Javascript script to embed and run at file opening'/>\n"
|
|
"</CreationOptionList>\n" );
|
|
|
|
#if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
|
|
poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST, pszOpenOptionList );
|
|
poDriver->pfnOpen = PDFDataset::Open;
|
|
poDriver->pfnIdentify = PDFDataset::Identify;
|
|
poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
|
|
#endif // HAVE_POPPLER || HAVE_PODOFO
|
|
|
|
poDriver->pfnCreateCopy = GDALPDFCreateCopy;
|
|
poDriver->pfnCreate = PDFWritableVectorDataset::Create;
|
|
poDriver->pfnUnloadDriver = GDALPDFUnloadDriver;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|