You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

460 lines
17 KiB

#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);
}