# include <regex>
# include <stack>
# include "modgmt_map.h"
# include "modgmt_func.h"
# include "modgmt_gsfuncs.h"
struct input_runtime
{
std : : list < std : : shared_ptr < std : : string > > : : const_iterator ci , end ;
size_t pos ;
} ;
static int gs_bbox_callback ( void * caller_handle , char * buf , int len )
{
struct input_runtime * r = static_cast < struct input_runtime * > ( static_cast < struct gs_runtime * > ( caller_handle ) - > indata ) ;
size_t buflen = static_cast < size_t > ( len ) ;
size_t inbuf = 0 ;
if ( r - > ci = = r - > end ) return 0 ;
while ( inbuf ! = buflen )
{
if ( ( * r - > ci ) - > length ( ) - r - > pos > = buflen - inbuf ) // Remainder of string greater or equal remainder of buffer
{
// Just copy part of string to buffer
memcpy ( buf + inbuf , ( * r - > ci ) - > c_str ( ) + r - > pos , buflen - inbuf ) ;
r - > pos + = buflen - inbuf ;
inbuf = buflen ;
}
else // Remainder of string lesser then remainder of buffer
{
// Copy remainder of string to buffer
memcpy ( buf + inbuf , ( * r - > ci ) - > c_str ( ) + r - > pos , ( * r - > ci ) - > length ( ) - r - > pos ) ;
inbuf + = ( * r - > ci ) - > length ( ) - r - > pos ;
// Go to next string
r - > ci + + ; r - > pos = 0 ;
if ( r - > ci = = r - > end ) break ; // No more strings, leave.
}
}
return inbuf ;
}
static inline int eps2pdf ( const std : : string & eps , std : : string * pdf , double res , uint ta = 4 , uint ga = 4 )
{
if ( ! gs_abilities . havepdf ) return 1 ; // No pdf support
return GhostRun ( " -r " + ToString ( res ) + " -dEPSCrop -dTextAlphaBits= " + ToString ( ta ) + " -dGraphicAlphaBits= " + ToString ( ga ) + " -dNOPLATFONT -dSubsetFonts=true -dEmbedAllFonts=true -dAutoFilterColorImages=false -dColorImageFilter=/FlateEncode -dAutoFilterGrayImages=false -dGrayImageFilter=/FlateEncode -dAutoFilterMonoImages=false -dMonoImageFilter=/CCITTFaxEncode -sDEVICE=pdfwrite -dMaxInlineImageSize=0 -c .setpdfwrite <</NeverEmbed [ ]>> setdistillerparams " , eps , 0 , 0 , pdf ) ;
}
// Creating eps map from sequence of layers
/*
Input :
If first argument is a string , then this string is a title of map ( % % Title parameter in eps file ) .
Other arguments are sequence of pairs and layers .
Pairs can be x , y , xr , yr , xg , yg , xgr , ygr .
x and y set shift for next layer in cm . This local shift overrides global ( setted by xg , yg ) .
xr and yr do the same but respect to global shift .
xg and yg set global shift ( for all subsequent layers ) .
xgr and ygr add corresponding values to global shift .
Resulted shift is added to own shift of layer ( setted by function LayerShift ) .
*/
const ObjectBase * GMT_Map ( const ObjectList * input )
{
std : : string err ;
std : : string title ;
double xg , yg , x , y ;
bool xislocal = false , yislocal = false ;
std : : list < std : : shared_ptr < std : : string > > data ;
xg = yg = x = y = 0.0 ;
// FIXME: Workaround ghostscript bug 202735
// See http://bugs.ghostscript.com/show_bug.cgi?id=202735
data . emplace_back ( new std : : string ( " 4000 4000 translate " ) ) ;
data . emplace_back ( new std : : string ( header ) ) ;
for ( auto i = input - > begin ( ) ; i ! = input - > end ( ) ; + + i )
{
// Check if first argument is string
if ( input - > begin ( ) = = i )
{
OBType < ObjectString > s ( * i ) ;
if ( s )
{
title = s - > Value ( ) ;
continue ;
}
}
// Check if argument is pair
{
OBType < ObjectPair > p ( * i ) ;
if ( p )
{
std : : string name = p - > Name ( ) ;
tolower ( name ) ;
Base2Double d ;
bool suc = true ;
double val = d . Convert ( p - > Value ( ) , & suc , err ) ;
if ( ! suc ) goto fail ; // Conversion failed
suc = false ;
if ( " x " = = name ) { suc = true ; xislocal = true ; x = val ; }
if ( " y " = = name ) { suc = true ; yislocal = true ; y = val ; }
if ( " xr " = = name ) { suc = true ; xislocal = true ; x = val + xg ; }
if ( " yr " = = name ) { suc = true ; yislocal = true ; y = val + yg ; }
if ( " xg " = = name ) { suc = true ; xg = val ; }
if ( " yg " = = name ) { suc = true ; yg = val ; }
if ( " xgr " = = name ) { suc = true ; xg + = val ; }
if ( " ygr " = = name ) { suc = true ; yg + = val ; }
if ( ! suc ) { err = " Unknown parameter name: " + p - > Name ( ) ; goto fail ; } // Unknown name
continue ;
}
}
// Check if argument is layer
{
OBType < ObjectGMTLayer > l ( * i ) ;
if ( l )
{
struct gmt_layer layer = l - > Data ( ) ;
layer . shiftx + = ( xislocal ? x : xg ) ;
layer . shifty + = ( yislocal ? y : yg ) ;
xislocal = yislocal = false ;
if ( layer . Shifted ( ) )
{
data . emplace_back ( new std : : string ( layer . BeginShift ( ) ) ) ;
data . emplace_back ( layer . data ) ;
data . emplace_back ( new std : : string ( layer . EndShift ( ) ) ) ;
}
else data . emplace_back ( layer . data ) ;
continue ;
}
}
err = " Unknown argument type: " + i - > Type ( ) ;
goto fail ; // Unknown type of argument
}
data . emplace_back ( new std : : string ( footer ) ) ;
// Calculate bounding box
int64_t bblx , bbly , bbrx , bbry ;
{
std : : string bboxes ;
struct input_runtime in = { data . begin ( ) , data . end ( ) , 0 } ;
int ret ;
ret = GhostRun ( " -sDEVICE=bbox " , gs_bbox_callback , & in , 0 , & bboxes , 0 ) ; // Bounding box is writed on stderr
if ( 0 ! = ret ) { err = " Can't determine BoundingBox " ; goto fail ; } // Something wrong
std : : smatch m ;
std : : smatch : : const_iterator ci ;
std : : regex r ( " %%BoundingBox:[[:space:]]+(-?[0-9]+)[[:space:]]+(-?[0-9]+)[[:space:]]+(-?[0-9]+)[[:space:]]+(-?[0-9]+) " ) ;
bool ok = true ;
std : : regex_search ( bboxes , m , r ) ;
if ( 5 ! = m . size ( ) ) { err = " Something go wrong, this is strange and scary " ; goto fail ; } // This is strange and scary
ci = m . cbegin ( ) ;
ci + + ; // Skip full match
ok = ok & & str2int ( * ci + + , & bblx ) ;
ok = ok & & str2int ( * ci + + , & bbly ) ;
ok = ok & & str2int ( * ci + + , & bbrx ) ;
ok = ok & & str2int ( * ci + + , & bbry ) ;
if ( ! ok ) { err = " Unexpected error! " ; goto fail ; } // Unexpected!
// FIXME: Workaround ghostscript bug 202735
// We add 5 points to each margin because ghostscript does'nt count linewidths when calculate bounding box
bblx - = 4005 ;
bbly - = 4005 ;
bbrx - = 3995 ;
bbry - = 3995 ;
}
data . pop_front ( ) ;
// Creating eps file
{
std : : string curtime ;
{
const size_t bufsize = 1024 ;
char buf [ bufsize ] ;
time_t sec ;
struct tm t ;
sec = time ( 0 ) ;
localtime_r ( & sec , & t ) ;
curtime . assign ( buf , strftime ( buf , bufsize , " %F %T %Z " , & t ) ) ;
}
std : : string * eps = new std : : string ( " %!PS-Adobe-3.0 EPSF-3.0 \n %%BoundingBox: " ) ;
* eps + = ToString ( bblx ) + " " + ToString ( bbly ) + " " + ToString ( bbrx ) + " " + ToString ( bbry ) ;
* eps + = " \n %%Title: " + ( title . empty ( ) ? std : : string ( " untitled " ) : title ) ;
* eps + = " \n %%Creator: gmt_makemap \n %%CreationDate: " + curtime ;
* eps + = " \n %%LanguageLevel: 2 \n %%Orientation: Portrait \n %%EndComments \n " ;
for ( auto d : data ) * eps + = * d ;
return new ObjectGMTMap ( eps , bblx , bbly , bbrx , bbry ) ;
}
fail :
return new ObjectError ( " Map " , err ) ;
}
// Creating pdf from eps
/*
Input :
One argument must be GMTMap .
Optionally , resolution can be specified by pair with name resolution , res or r and double value . Default is 720.
*/
const ObjectBase * GMT_Convert2PDF ( const ObjectList * input )
{
std : : string err ;
RNFPar < SearchGMTMap , ObjectGMTMap > map ( " map " ) ;
ONPar < Base2PosD > r ( " resolution " , 720 ) ;
ParseNamedParameters params ( input , map , r ) ;
const ObjectGMTMap * m = map ;
if ( ! gs_abilities . havepdf ) { err = " No PDF support in Ghostscript " ; goto fail ; } // No pdf support
if ( ! params ) { err = params . Error ( ) ; goto fail ; } // Parsing error
{
std : : string * out = new std : : string ;
int ret = eps2pdf ( * ( m - > pValue ( ) ) , out , r ) ;
if ( 0 ! = ret ) { err = " Conversion of EPS to PDF failed " ; delete out ; goto fail ; } // Something wrong
return new ObjectGMTMapPDF ( out , m - > Bblx ( ) , m - > Bbly ( ) , m - > Bbrx ( ) , m - > Bbry ( ) ) ;
}
fail :
return new ObjectError ( " EPS2PDF " , err ) ;
}
// Creating png from eps or pdf
/*
Input :
One argument must be GMTMap or GMTMapPDF .
Other parameter are optional name - value pairs .
resolution , res or r - output resolution ( pixels / inch ) . Default : 300.
Instead of resolution , width ( width or w ) or height ( height or h ) in pixels may be specified . Resolution , width and height are mutually exclusive . Large value of resolution may result in ghostscript error .
colorspace , cspace or cs - is a string , one of " mono " or " monochrome " ( black and white image ) , " monodiffused " or " monod " ( black and white image made from grayscale by error diffusion ) , " 16c " or " 16 " ( 16 colors ) , " 256c " or " 256 " ( 256 colors ) , " gray " , " grey " or " g " ( 256 shades of gray ) , " full " , " 8bit " or " color " ( 8 - bit per component rgb ) . Some of these values may return error , this depends on ghostscript setup in system . Default : " color " .
For colorspaces " monod " , " color " and " gray " may be also specified parameter downscale ( dscale or ds ) with small integer value ( from 1 to 10 ) . The image initially rendered in dscale * res resolution and downscaling to res resolution . This increase output quality ( lines and contours are more " soft " ) , but may result in ghostscript error if rendering resolution is too high .
textantialiasing or taa - is a string , can be " none " ( " no " ) , " small " ( " s " ) or " full " ( " f " ) . Controls text antialiasing . Default : " full " .
graphicsantialiasing or gaa - is a string , can be " none " , " small " or " full " . Controls graphics antialiasing . Default : " full " .
antialiasing or aa - can be used to set graphics and text antialiasing simultaneously .
*/
const ObjectBase * GMT_Convert2PNG ( const ObjectList * input )
{
std : : string gsdev , err ;
double res ;
ONPar < Base2PosD > r ( " resolution " , 300 ) , ds ( " d[own]scale " , 1.0 ) ;
ONPar < Base2Pos > w ( " width " ) , h ( " height " ) ;
ONPar < Base2StringD > cs ( " c[olor][ ]space " , " color " ) , taa ( " t[ext]a[nti]aliasing " , " full " ) , gaa ( " g[raphics]a[nti]aliasing " , " full " ) , aa ( " a[nti]aliasing " , " full " ) ;
const ObjectGMTMap * me ;
const ObjectGMTMapPDF * mp ;
const GMTMap * m ;
uint aat , aag ;
uint dscale ;
if ( ! gs_abilities . havepdf ) { err = " No PDF support in Ghostscript " ; goto fail ; } // No pdf support, so, no png
// Search map
{
RNFPar < SearchGMTMap , ObjectGMTMap > mapeps ( " map " ) ;
RNFPar < SearchGMTMapPDF , ObjectGMTMap > mappdf ( " map " ) ;
ParseNamedParameters peps ( input , mapeps ) ;
ParseNamedParameters ppdf ( input , mappdf ) ;
if ( ! ( peps | | ppdf ) ) { err = " Error parsing map parameter " ; goto fail ; }
me = mapeps ; mp = mappdf ;
if ( nullptr = = me & & nullptr = = mp ) { err = " Map parameter not specified " ; goto fail ; } // This must never happened
if ( nullptr ! = me ) m = me ; else m = mp ;
}
// Parse other parameters
{
ParseNamedParameters params ( input , r , ds , w , h , cs , taa , gaa , aa ) ;
if ( ! params ) { err = params . Error ( ) ; goto fail ; }
}
// Determine resolution
{
// Check existence
if ( ( r . Exist ( ) & & w . Exist ( ) ) | | ( r . Exist ( ) & & h . Exist ( ) ) | | ( w . Exist ( ) & & h . Exist ( ) ) ) { err = " Only one of resolution, width or height may be specified " ; goto fail ; } // Only one parameter allowed
if ( r . Exist ( ) ) res = r ;
if ( w . Exist ( ) ) res = w * 72.0 / ( m - > Bbrx ( ) - m - > Bblx ( ) ) ;
if ( h . Exist ( ) ) res = h * 72.0 / ( m - > Bbry ( ) - m - > Bbly ( ) ) ;
}
// Color model
{
std : : string cspace = cs ;
tolower ( cspace ) ;
if ( ( " mono " = = cspace | | " monochrome " = = cspace ) & & gs_abilities . havepngmono ) gsdev = " pngmono " ;
if ( ( " monod " = = cspace | | " monodiffused " = = cspace ) & & gs_abilities . havepngmonod ) gsdev = " pngmonod " ;
if ( ( " 16c " = = cspace | | " 16 " = = cspace ) & & gs_abilities . havepng16 ) gsdev = " png16 " ;
if ( ( " 256c " = = cspace | | " 256 " = = cspace ) & & gs_abilities . havepng256 ) gsdev = " png256 " ;
if ( ( " gray " = = cspace | | " grey " = = cspace | | " g " = = cspace ) & & gs_abilities . havepnggray ) gsdev = " pnggray " ;
if ( ( " full " = = cspace | | " 8bit " = = cspace | | " color " = = cspace ) & & gs_abilities . havepng16m ) gsdev = " png16m " ;
if ( gsdev . empty ( ) ) { err = " Colorspace " + cs . Value ( ) + " is bad or unsupported " ; goto fail ; } // Incorrect value
}
// Antialiasing
{
std : : string aags = aa , aats = aa ;
if ( taa . Exist ( ) ) aats = taa ;
if ( gaa . Exist ( ) ) aags = gaa ;
tolower ( aags ) ; tolower ( aats ) ;
if ( " none " = = aags | | " no " = = aags | | " n " = = aags ) aag = 1 ; if ( " none " = = aats | | " no " = = aats | | " n " = = aats ) aat = 1 ;
if ( " small " = = aags | | " s " = = aags ) aag = 2 ; if ( " small " = = aats | | " s " = = aats ) aat = 2 ;
if ( " full " = = aags | | " f " = = aags ) aag = 4 ; if ( " full " = = aats | | " f " = = aats ) aat = 4 ;
if ( 0 = = aat ) { err = " Incorrect value for text antialiasing: " + aats ; goto fail ; }
if ( 0 = = aag ) { err = " Incorrect value for graphics antialiasing: " + aags ; goto fail ; }
}
// Downscale
if ( " pngmonod " = = gsdev | | " pnggray " = = gsdev | | " png16m " = = gsdev )
{
dscale = static_cast < uint > ( ds ) ;
if ( dscale < 1 | | dscale > 10 ) { err = " Downscale must be in interval from 1 to 10 " ; goto fail ; }
res * = dscale ;
}
// Go!
{
std : : string * pdf = 0 ;
int ret ;
if ( nullptr ! = me )
{
pdf = new std : : string ;
ret = eps2pdf ( * ( me - > pValue ( ) ) , pdf , r , aat , aag ) ;
if ( 0 ! = ret ) { err = " Can't convert EPS to PDF " ; delete pdf ; goto fail ; } // Something wrong
}
{
std : : string * out = new std : : string ;
ret = GhostRun ( " -r " + ToString ( res ) + " -dTextAlphaBits= " + ToString ( aat ) + " -dGraphicAlphaBits= " + ToString ( aag ) + " -sDEVICE= " + gsdev + ( ( dscale > 1 ) ? ( " -dDownScaleFactor= " + ToString ( dscale ) ) : " " ) , ( ( 0 ! = pdf ) ? * pdf : * ( mp - > pValue ( ) ) ) , 0 , 0 , out ) ;
if ( 0 ! = pdf ) delete pdf ;
if ( 0 ! = ret ) { err = " Can't convert PDF to PNG " ; delete out ; goto fail ; } // Something wrong
return new ObjectGMTImage ( out ) ;
}
}
fail :
return new ObjectError ( " EPS2PNG " , err ) ;
}
// Creating jpeg from eps or pdf
/*
Input :
One argument must be GMTMap or GMTMapPDF .
Other parameter are optional name - value pairs .
resolution , res or r - output resolution ( pixels / inch ) . Default : 300.
Instead of resolution , width ( width or w ) or height ( height or h ) in pixels may be specified . Resolution , width and height are mutually exclusive . Large value of resolution may result in ghostscript error .
colorspace , cspace or cs - is a string , one " gray " , " grey " or " g " ( 256 shades of gray ) , " full " , " 8bit " or " color " ( 8 - bit per component rgb ) . Some of these values may return error , this depends on ghostscript setup in system . Default : " color " .
quality , qual or q - JPEG quality level , integer from 0 to 100. Default : 75.
textantialiasing or taa - is a string , can be " none " ( " no " ) , " small " ( " s " ) or " full " ( " f " ) . Controls text antialiasing . Default : " full " .
graphicsantialiasing or gaa - is a string , can be " none " , " small " or " full " . Controls graphics antialiasing . Default : " full " .
antialiasing or aa - can be used to set graphics and text antialiasing simultaneously .
*/
const ObjectBase * GMT_Convert2JPG ( const ObjectList * input )
{
std : : string gsdev , err ;
ONPar < Base2PosD > r ( " resolution " , 300 ) ;
ONPar < Base2Pos > w ( " width " ) , h ( " height " ) ;
ONPar < Base2StringD > cs ( " c[olor][ ]space " , " color " ) , taa ( " t[ext]a[nti]aliasing " , " full " ) , gaa ( " g[raphics]a[nti]aliasing " , " full " ) , aa ( " a[nti]aliasing " , " full " ) ;
ONPar < Base2NonNegD > q ( " quality " , 75 ) ;
const ObjectGMTMap * me ;
const ObjectGMTMapPDF * mp ;
const GMTMap * m ;
double res ;
uint aat , aag ;
uint qual ;
if ( ! gs_abilities . havepdf ) { err = " No PDF support in Ghostscript " ; goto fail ; } // No pdf support, so, no jpeg
// Search map
{
RNFPar < SearchGMTMap , ObjectGMTMap > mapeps ( " map " ) ;
RNFPar < SearchGMTMapPDF , ObjectGMTMap > mappdf ( " map " ) ;
ParseNamedParameters peps ( input , mapeps ) ;
ParseNamedParameters ppdf ( input , mappdf ) ;
if ( ! ( peps | | ppdf ) ) { err = " Error parsing map parameter " ; goto fail ; }
me = mapeps ; mp = mappdf ;
if ( nullptr = = me & & nullptr = = mp ) { err = " Map parameter not specified " ; goto fail ; } // This must never happened
if ( nullptr ! = me ) m = me ; else m = mp ;
}
// Parse other parameters
{
ParseNamedParameters params ( input , r , q , w , h , cs , taa , gaa , aa ) ;
if ( ! params ) { err = params . Error ( ) ; goto fail ; }
}
// Determine resolution
{
// Check existence
if ( ( r . Exist ( ) & & w . Exist ( ) ) | | ( r . Exist ( ) & & h . Exist ( ) ) | | ( w . Exist ( ) & & h . Exist ( ) ) ) { err = " Only one of resolution, width or height may be specified " ; goto fail ; } // Only one parameter allowed
if ( r . Exist ( ) ) res = r ;
if ( w . Exist ( ) ) res = w * 72.0 / ( m - > Bbrx ( ) - m - > Bblx ( ) ) ;
if ( h . Exist ( ) ) res = h * 72.0 / ( m - > Bbry ( ) - m - > Bbly ( ) ) ;
}
// Color model
{
std : : string cspace = cs ;
tolower ( cspace ) ;
if ( ( " gray " = = cspace | | " grey " = = cspace | | " g " = = cspace ) & & gs_abilities . havejpeggray ) gsdev = " jpeggray " ;
if ( ( " full " = = cspace | | " 8bit " = = cspace | | " color " = = cspace ) & & gs_abilities . havejpeg ) gsdev = " jpeg " ;
if ( gsdev . empty ( ) ) { err = " Colorspace " + cs . Value ( ) + " is bad or unsupported " ; goto fail ; } // Incorrect value
}
// Antialiasing
{
std : : string aags = aa , aats = aa ;
if ( taa . Exist ( ) ) aats = taa ;
if ( gaa . Exist ( ) ) aags = gaa ;
tolower ( aags ) ; tolower ( aats ) ;
if ( " none " = = aags | | " no " = = aags | | " n " = = aags ) aag = 1 ; if ( " none " = = aats | | " no " = = aats | | " n " = = aats ) aat = 1 ;
if ( " small " = = aags | | " s " = = aags ) aag = 2 ; if ( " small " = = aats | | " s " = = aats ) aat = 2 ;
if ( " full " = = aags | | " f " = = aags ) aag = 4 ; if ( " full " = = aats | | " f " = = aats ) aat = 4 ;
if ( 0 = = aat ) { err = " Incorrect value for text antialiasing: " + aats ; goto fail ; }
if ( 0 = = aag ) { err = " Incorrect value for graphics antialiasing: " + aags ; goto fail ; }
}
// Quality
{
qual = static_cast < uint > ( q ) ;
if ( q > 100 ) { err = " JPEG quality must not exceed 100 " ; goto fail ; } // Error
}
// Go!
{
std : : string * pdf = 0 ;
int ret ;
if ( nullptr ! = me )
{
pdf = new std : : string ;
ret = eps2pdf ( * ( me - > pValue ( ) ) , pdf , r , aat , aag ) ;
if ( 0 ! = ret ) { err = " Can't convert EPS to PDF " ; delete pdf ; goto fail ; } // Something wrong
}
{
std : : string * out = new std : : string ;
ret = GhostRun ( " -r " + ToString ( res ) + " -dTextAlphaBits= " + ToString ( aat ) + " -dGraphicAlphaBits= " + ToString ( aag ) + " -sDEVICE= " + gsdev + " -dJPEGQ= " + ToString ( qual ) , ( ( 0 ! = pdf ) ? * pdf : * ( mp - > pValue ( ) ) ) , 0 , 0 , out ) ;
if ( 0 ! = pdf ) delete pdf ;
if ( 0 ! = ret ) { err = " Can't convert PDF to JPEG " ; delete out ; goto fail ; } // Something wrong
return new ObjectGMTImage ( out ) ;
}
}
fail :
return new ObjectError ( " EPS2JPEG " , err ) ;
}