|
|
|
#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).
|
|
|
|
*/
|
|
|
|
ObjectBase* GMT_Map(const ObjectList* input)
|
|
|
|
{
|
|
|
|
const ObjectList* list=input;
|
|
|
|
auto size=list->Size();
|
|
|
|
if(0==size) return 0;
|
|
|
|
decltype(size) pos=0;
|
|
|
|
std::string title;
|
|
|
|
std::stack<std::pair<const ObjectList*,decltype(pos)> > lstack;
|
|
|
|
bool first=true;
|
|
|
|
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));
|
|
|
|
while(pos<size)
|
|
|
|
{
|
|
|
|
// Check if next argument is list
|
|
|
|
{
|
|
|
|
OBType<ObjectList> l(list->At(pos));
|
|
|
|
// Descending
|
|
|
|
if(l)
|
|
|
|
{
|
|
|
|
if(0==l->Size()) goto next;
|
|
|
|
lstack.push(std::make_pair(list,pos));
|
|
|
|
list=l;
|
|
|
|
pos=0;
|
|
|
|
size=l->Size();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if first argument is string
|
|
|
|
if(first)
|
|
|
|
{
|
|
|
|
first=false;
|
|
|
|
OBType<ObjectString> s(list->At(pos));
|
|
|
|
if(s)
|
|
|
|
{
|
|
|
|
title=s->Value();
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if argument is pair
|
|
|
|
{
|
|
|
|
OBType<ObjectPair> p(list->At(pos));
|
|
|
|
if(p)
|
|
|
|
{
|
|
|
|
std::string name=p->Name();
|
|
|
|
tolower(name);
|
|
|
|
Base2Double d(p->Value());
|
|
|
|
bool suc=true;
|
|
|
|
double val=d(&suc);
|
|
|
|
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) goto fail; // Unknown name
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if argument is layer
|
|
|
|
{
|
|
|
|
OBType<ObjectGMTLayer> l(list->At(pos));
|
|
|
|
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);
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
goto fail; // Unknown type of argument
|
|
|
|
next:
|
|
|
|
pos++;
|
|
|
|
// Ascending
|
|
|
|
if(pos==size && !lstack.empty())
|
|
|
|
{
|
|
|
|
list=lstack.top().first;
|
|
|
|
pos=lstack.top().second;
|
|
|
|
lstack.pop();
|
|
|
|
size=list->Size();
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) 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()) 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) 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 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
*/
|
|
|
|
ObjectBase* GMT_Convert2PDF(const ObjectList* input)
|
|
|
|
{
|
|
|
|
if(!gs_abilities.havepdf) return 0; // No pdf support
|
|
|
|
double r;
|
|
|
|
bool suc=true;
|
|
|
|
const ObjectGMTMap* map=0;
|
|
|
|
|
|
|
|
{
|
|
|
|
SearchParameterWDefO<double,DoubleDefaultVal<720>,false,PMin<1> > res(input,"r","res","resolution");
|
|
|
|
r=res(&suc);
|
|
|
|
if(!suc) goto fail; // Error
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
for(ObjectList::ListValues::size_type i=0;i<input->Size();i++)
|
|
|
|
{
|
|
|
|
OBType<ObjectGMTMap> m(input->At(i));
|
|
|
|
if(m)
|
|
|
|
{
|
|
|
|
if(0!=map) goto fail; // Duplicate
|
|
|
|
map=m;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(0==map) goto fail; // Map not found
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::string* out=new std::string;
|
|
|
|
int ret=eps2pdf(*(map->pValue()),out,r);
|
|
|
|
if(0!=ret) { delete out; goto fail; } // Something wrong
|
|
|
|
return new ObjectGMTMapPDF(out,map->Bblx(),map->Bbly(),map->Bbrx(),map->Bbry());
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|