#ifndef MODGMT_STRUCT_H #define MODGMT_STRUCT_H #include #include #include #include #include "common.h" #include "modgmt_colornames.h" #include "modgmt_strcomp.h" // Centimeters to GMT points scale factor // 1 inch = 72 pt = 2.54 cm --> 1 cm = 72/2.54 pt // GMT's own scaling factor is 0.06 inline static double cm2GMT(double cm) { static const double scale=(72.0/2.54)/0.06; return cm*scale; } // Helper template for checking double numbers acquired from Source with some Policies. // Main definition, never instantiated template class GetDouble; // Recursive definition template class GetDouble: public GetDouble { Policy p; public: GetDouble(GetDouble&&) = delete; GetDouble(GetDouble&) = delete; template GetDouble(Args... args):GetDouble(args...) {}; double operator()(bool* suc, std::string& err) const {return p(GetDouble::operator()(suc,err),suc,err);} }; // Bottom of recursion template class GetDouble: public Source { public: GetDouble(GetDouble&&) = delete; GetDouble(GetDouble&) = delete; GetDouble() = delete; template GetDouble(Args... args):Source(args...) {}; double operator()(bool* suc, std::string& err) const {return Source::operator()(suc, err);} }; // We use rational representation of floating point number, because double type values not allowed as template parameter // Policy to check if value is greater or equal num/denum template class PMin { static bool Compare(double d1, double d2) {return equal?(d1<=d2):(d1(num)/denum)) { *suc=false; if(err.empty()) err="Value "+ToString(v)+" must be greater "+(equal?"or equal ":"")+"then "+ToString(num)+((denum==1)?"":("/"+ToString(denum))); } return v; } }; // Policy to check if value is lesser or equal num/denum template class PMax { static bool Compare(double d1, double d2) {return equal?(d1>=d2):(d1>d2);} public: double operator()(double v, bool* suc, std::string& err) const { if(Compare(v,static_cast(num)/denum)) { *suc=false; if(err.empty()) err="Value "+ToString(v)+" must be lesser "+(equal?"or equal ":"")+"then "+ToString(num)+((denum==1)?"":("/"+ToString(denum))); } return v; } }; // Get double value from string or number class SourceValue { double d; std::string err; public: SourceValue(const std::string& s) {if(!str2double(s,&d)) err="Can't convert string \""+s+"\" to double value";} SourceValue(double s):d(s) {} double operator()(bool* suc, std::string& ierr) const { if(!err.empty()) {*suc=false; ierr=err;} return d; } }; // Helper types for colors typedef GetDouble,PMax<255> > Value2RGB; typedef GetDouble,PMax<360> > Value2Hue; typedef GetDouble,PMax< 1 > > Value2SV; typedef GetDouble,PMax<100> > Value2CMYK; typedef GetDouble,PMax<100> > Value2Transp; // Helper type for pens and dashes typedef GetDouble > Value2Width; class gmt_struct {}; // Base type for all gmt structures // Coordinate struct gmt_coord: public gmt_struct { bool isdeg; union { double r; struct { bool sign; uint16_t d; uint8_t m; double s; }; }; std::string Value() const { if(!isdeg) return ToString(r); else return (sign?"":"-")+ToString(d)+":"+ToString(m)+(s==0?"":":"+ToString(s)); } operator double() const { if(isdeg) return (d+(m+s/60.0)/60.0)*(sign?1:-1); else return r; } bool Convert(const std::string& str, std::string& err) { WordList wl; WordList::const_iterator cw; wl=Split(str,":",true); if(1==wl.size()) // No dd:mm { isdeg=false; bool res=str2double(str,&r); if(!res) err="Can't convert string "+str+" to double value"; return res; } if(0==wl.size() || wl.size()>3) {err="String "+str+" have incorrect format for geographic coordinate, must be ddd[:mm[:ss[.fff]]]"; return false;} isdeg=true; int64_t res; // degrees cw=wl.begin(); if(!str2int(*cw,&res)) {err="Can't convert "+*cw+" to integer"; return false;} if(res>360 || res<-360) res%=360; sign=(std::string::npos==cw->find('-')); d=static_cast(sign?res:-res); // minutes cw++; if(!str2int(*cw,&res)) {err="Can't convert "+*cw+" to integer"; return false;} if(res<0 || res>=60) {err="Minutes must be not lesser then 0 and lesser then 60"; return false;} m=static_cast(res); s=0; // seconds cw++; if(wl.end()==cw) return true; // No seconds if(!str2double(*cw,&s) ) {err="Can't convert "+*cw+" to double"; return false;} if(s<0.0 || s>=60.0) {err="Seconds must be not lesser then 0 and lesser then 60"; return false;} return true; } bool Convert(double num, std::string& err) { isdeg=false; r=num; return true; } }; // Region struct gmt_region: public gmt_struct { enum Type {NORMAL,BBOX,GLOBAL360,GLOBAL180}; Type type; struct gmt_coord xb,xe,yb,ye; std::string Value() const { switch(type) { case(NORMAL): return std::string("-R")+xb.Value()+"/"+xe.Value()+"/"+yb.Value()+"/"+ye.Value(); case(BBOX): return std::string("-R")+xb.Value()+"/"+yb.Value()+"/"+xe.Value()+"/"+ye.Value()+"r"; case(GLOBAL360): return "-Rg"; case(GLOBAL180): return "-Rd"; } return ""; } // Only "global", "global180" and "global360" // TODO: add parsing of "-R" strings bool Convert(const std::string& istr, std::string& err) { std::string str=istr; tolower(str); if("global180"==str) { type=GLOBAL180; xb.Convert(-180.0,err); xe.Convert(180.0,err); yb.Convert(-90.0,err); ye.Convert(90.0,err); return true; } if("global360"==str || "global"==str) { type=GLOBAL360; xb.Convert(0.0,err); xe.Convert(360.0,err); yb.Convert(-90.0,err); ye.Convert(90.0,err); return true; } err="Can't convert "+istr+" to region."; return false; } // Make by coordinates bool Convert(struct gmt_coord ixb, struct gmt_coord ixe, struct gmt_coord iyb, struct gmt_coord iye, bool isbbox=false) { type=isbbox?BBOX:NORMAL; xb=ixb; yb=iyb; xe=ixe; ye=iye; return true; } }; // Projection struct gmt_projection: public gmt_struct { // OBLIQMERCATOR types enum class OType {NOTDEF,A,B,C}; // No UTM (-Ju) enum projection {NOTDEF,XY,CYL_EQU,MERCATOR,TRANSMERCATOR,OBLIQMERCATOR,CASSINI,CYL_EQA,MILLER,CYL_STERE}; // Real size in cm of drawing area double rwidth,rheight; struct gmt_region region; projection proj; double width; // parameter of projection static const double default_width; union // other projection parameters { // XY struct {double height;} x; // Cylindrical projections // CYL_EQU (Cylindrical Equidistant -Jq) struct {struct gmt_coord stpar,cmer;} q; // MERCATOR (-Jm) struct {struct gmt_coord stpar,cmer;} m; // TRANSMERCATOR (-Jt) struct {struct gmt_coord cmer,orlat; double scale;} t; // OBLIQMERCATOR (-Jo) struct { OType type; struct gmt_coord clon,clat; union { struct gmt_coord azimuth; // A struct {struct gmt_coord eqlon,eqlat;}; // B struct {struct gmt_coord polelon,polelat;}; // C }; } o; // CASSINI (-Jc) struct {struct gmt_coord clon,clat;} c; // CYL_EQA (Cylindrical equal-area -Jy) struct {struct gmt_coord stpar,cmer;} y; // MILLER (-Jj) struct {struct gmt_coord cmer;} j; // CYL_STERE (Cylindrical stereographic -Jcyl_stere) struct {struct gmt_coord stpar,cmer;} cyl_stere; }; std::string Value() const { std::string ret; switch(proj) { case(XY): {ret="-JX"+ToString(width)+"c/"+ToString(x.height)+"c"; break;} case(CYL_EQU): {ret="-JQ"+q.cmer.Value()+"/"+q.stpar.Value()+"/"+ToString(width)+"c"; break;} case(MERCATOR): {ret="-JM"+m.cmer.Value()+"/"+m.stpar.Value()+"/"+ToString(width)+"c"; break;} case(TRANSMERCATOR): {ret="-JT"+t.cmer.Value()+"/"+t.orlat.Value()+"/"+ToString(width)+"c --PROJ_SCALE_FACTOR="+ToString(t.scale); break;} case(OBLIQMERCATOR): { switch(o.type) { case(OType::A): {ret="-JOa"+o.clon.Value()+"/"+o.clat.Value()+"/"+o.azimuth.Value()+"/"+ToString(width)+"c"; break;} case(OType::B): {ret="-JOb"+o.clon.Value()+"/"+o.clat.Value()+"/"+o.eqlon.Value()+"/"+o.eqlat.Value()+"/"+ToString(width)+"c"; break;} case(OType::C): {ret="-JOb"+o.clon.Value()+"/"+o.clat.Value()+"/"+o.polelon.Value()+"/"+o.polelat.Value()+"/"+ToString(width)+"c"; break;} default: return ""; } break; } case(CASSINI): {ret="-JC"+c.clon.Value()+"/"+c.clat.Value()+"/"+ToString(width)+"c"; break;} case(CYL_EQA): {ret="-JY"+y.cmer.Value()+"/"+y.stpar.Value()+"/"+ToString(width)+"c"; break;} case(MILLER): {ret="-JJ"+j.cmer.Value()+"/"+ToString(width)+"c"; break;} case(CYL_STERE): {ret="-JCyl_stere/"+cyl_stere.stpar.Value()+"/"+cyl_stere.cmer.Value()+"/"+ToString(width)+"c"; break;} default: return ""; } ret+=" "+region.Value(); return ret; } bool SetType(const std::string& s, std::string& err) { proj=NOTDEF; std::string str=s; tolower(str); // Handle one symbol cases if(str.size()==1) switch(str[0]) { case('c'): {proj=CASSINI; return true;} case('j'): {proj=MILLER; return true;} case('m'): {proj=MERCATOR; return true;} case('o'): {proj=OBLIQMERCATOR; return true;} case('q'): {proj=CYL_EQU; return true;} case('t'): {proj=TRANSMERCATOR; return true;} case('x'): {proj=XY; return true;} case('y'): {proj=CYL_EQA; return true;} default: break; } // Handle long GMT names if("cyl_stere"==str) {proj=CYL_STERE; return true;} // Handle common names std::string t1; for(const auto& m: projnames) { TemplateComparator cmp(m.first); bool res=cmp.Compare(str); if(!res) continue; if(NOTDEF!=proj) // Ambiguos string { err="Ambiguous projection definition: did you mean \""+t1+"\" or \""+cmp.Template2Name()+"\"?"; proj=NOTDEF; return false; } t1=cmp.Template2Name(); proj=m.second; } if(NOTDEF!=proj) return true; // All Ok err="Unknown projection: "+s; return false; } static void FillProjNames(); private: static std::map projnames; }; // Color struct gmt_color: public gmt_struct { public: enum ColorModel {RGB,GRAY,HSV,CMYK}; ColorModel model; union { double gray; // 0-255 struct {double r,g,b;}; // 0-255 struct {double hue,saturation,value;}; // 0-360, 0-1, 0-1 struct {double cyan,magenta,yellow,black;}; // 0-100 }; double transparency; std::string Value() const { std::string trans=(transparency!=0)?("@"+ToString(transparency)):""; switch(model) { case(RGB): return ToString(r)+"/"+ToString(g)+"/"+ToString(b)+trans; case(GRAY): return ToString(gray)+trans; case(HSV): return ToString(hue)+"-"+ToString(saturation)+"-"+ToString(value)+trans; case(CMYK): return ToString(cyan)+"/"+ToString(magenta)+"/"+ToString(yellow)+"/"+ToString(black)+trans; } return ""; } double Gray() const { switch(model) { case(RGB): return RGB2Gray().gray; case(GRAY): return gray; case(HSV): return HSV2Gray().gray; case(CMYK): return CMYK2Gray().gray; } return 0; } void ToGray() { switch(model) { case(RGB): gray=RGB2Gray().gray; break; case(GRAY): return; case(HSV): gray=HSV2Gray().gray; break; case(CMYK): gray=CMYK2Gray().gray; break; } model=GRAY; } double R() const { switch(model) { case(RGB): return r; case(GRAY): return Gray2RGB().r; case(HSV): return HSV2RGB().r; case(CMYK): return CMYK2RGB().r; } return 0; } double G() const { switch(model) { case(RGB): return g; case(GRAY): return Gray2RGB().g; case(HSV): return HSV2RGB().g; case(CMYK): return CMYK2RGB().g; } return 0; } double B() const { switch(model) { case(RGB): return b; case(GRAY): return Gray2RGB().b; case(HSV): return HSV2RGB().b; case(CMYK): return CMYK2RGB().b; } return 0; } void ToRGB() { gmt_color c; switch(model) { case(RGB): return; case(GRAY): c=Gray2RGB(); break; case(HSV): c=HSV2RGB(); break; case(CMYK): c=CMYK2RGB(); break; } model=RGB; r=c.r; g=c.g; b=c.b; } double H() const { switch(model) { case(RGB): return RGB2HSV().hue; case(GRAY): return Gray2HSV().hue; case(HSV): return hue; case(CMYK): return CMYK2HSV().hue; } return 0;// Own functions } double S() const { switch(model) { case(RGB): return RGB2HSV().saturation; case(GRAY): return Gray2HSV().saturation; case(HSV): return saturation; case(CMYK): return CMYK2HSV().saturation; } return 0; } double V() const { switch(model) { case(RGB): return RGB2HSV().value; case(GRAY): return Gray2HSV().value; case(HSV): return value; case(CMYK): return CMYK2HSV().value; } return 0; } void ToHSV() { gmt_color c; switch(model) { case(RGB): c=RGB2HSV(); break; case(GRAY): c=Gray2HSV(); break; case(HSV): return; case(CMYK): c=CMYK2HSV(); break; } model=HSV; hue=c.hue; saturation=c.saturation; value=c.value; } double C() const { switch(model) { case(RGB): return RGB2CMYK().cyan; case(GRAY): return Gray2CMYK().cyan; case(HSV): return HSV2CMYK().cyan; case(CMYK): return cyan; } return 0; } double M() const { switch(model) { case(RGB): return RGB2CMYK().magenta; case(GRAY): return Gray2CMYK().magenta; case(HSV): return HSV2CMYK().magenta; case(CMYK): return magenta; } return 0; } double Y() const { switch(model) { case(RGB): return RGB2CMYK().yellow; case(GRAY): return Gray2CMYK().yellow; case(HSV): return HSV2CMYK().yellow; case(CMYK): return yellow; } return 0; } double K() const { switch(model) { case(RGB): return RGB2CMYK().black; case(GRAY): return Gray2CMYK().black; case(HSV): return HSV2CMYK().black; case(CMYK): return black; } return 0; } void ToCMYK() { gmt_color cc; switch(model) { case(RGB): cc=RGB2CMYK(); break; case(GRAY): cc=Gray2CMYK(); break; case(HSV): cc=HSV2CMYK(); break; case(CMYK): return; } model=CMYK; cyan=cc.cyan; magenta=cc.magenta; yellow=cc.yellow; black=cc.black; } void ToModel(ColorModel newmodel) { switch(newmodel) { case(RGB): ToRGB(); return; case(GRAY): ToGray(); return; case(HSV): ToHSV(); return; case(CMYK): ToCMYK(); return; } } // Interpret one numeric argument as gray value bool Convert(double gr, std::string& err) { Value2RGB g(gr); bool suc=true; model=GRAY; transparency=0.0; gray=g(&suc,err); return suc; } bool Convert(const std::string& istr, std::string& err) { std::string cstr=istr; tolower(cstr); transparency=0.0; // Transparency check { WordList wl=Split(cstr,"@"); if(1!=wl.size() && 2!=wl.size()) return false; if(2==wl.size()) { WordList::const_iterator ci=wl.begin(); bool suc=true; cstr=*ci; ci++; Value2Transp t(*ci); transparency=t(&suc,err); if(!suc) return false; // Parse error } } { WordList wl_slash=Split(cstr,"/"),wl_hyphen=Split(cstr,"-"); WordList::size_type slash_size=wl_slash.size(),hyphen_size=wl_hyphen.size(); // Checks if(slash_size>1 && hyphen_size>1) {err="Delimeter of color components must be \"/\" or \"-\", not both"; return false;} if(2==slash_size || slash_size>4) {err="Number of color components must be 1 for GRAY, 3 for RGB or 4 for CMYK"; return false;} if(2==hyphen_size || hyphen_size>3) {err="There is 3 color components in HSV model"; return false;} // Gray or name // TODO: Hex representation if(1==slash_size && 1==hyphen_size) { if(SetByName(cstr)) return true; // Set color by name, all ok Value2RGB g(cstr); bool suc=true; model=GRAY; gray=g(&suc,err); err="Can't understand string "+cstr+", this is nor color name, nor gray value (0-255)"; return suc; } // RGB if(3==slash_size) { Value2RGB red(wl_slash.front()); wl_slash.pop_front(); Value2RGB green(wl_slash.front()); wl_slash.pop_front(); Value2RGB blue(wl_slash.front()); bool suc=true; model=RGB; r=red(&suc,err); if(!suc) return suc; g=green(&suc,err);if(!suc) return suc; b=blue(&suc,err); return suc; } // HSV if(3==hyphen_size) { Value2Hue h(wl_hyphen.front()); wl_hyphen.pop_front(); Value2SV s(wl_hyphen.front()); wl_hyphen.pop_front(); Value2SV v(wl_hyphen.front()); bool suc=true; model=HSV; hue=h(&suc,err); if(!suc) return suc; saturation=s(&suc,err);if(!suc) return suc; value=v(&suc,err); return suc; } // CMYK if(4==slash_size) { Value2CMYK c(wl_slash.front()); wl_slash.pop_front(); Value2CMYK m(wl_slash.front()); wl_slash.pop_front(); Value2CMYK y(wl_slash.front()); wl_slash.pop_front(); Value2CMYK k(wl_slash.front()); bool suc=true; model=CMYK; cyan=c(&suc,err); if(!suc) return suc; magenta=m(&suc,err);if(!suc) return suc; yellow=y(&suc,err); if(!suc) return suc; black=k(&suc,err); return suc; } } return false; } bool SetByName(const std::string& name) { const struct colorname* cdata; model=RGB; cdata=ColorHash::in_colors_set(name.c_str(),name.length()); if(0==cdata) return false; r=cdata->r; g=cdata->g; b=cdata->b; return true; } // Transformation functions private: #include "modgmt_colortransform.h" }; // Dash struct gmt_dash: public gmt_struct { std::vector dash; double shift; double width; bool wisrel; static const double default_width; std::string Value() const { std::string ret; if(dash.empty()) return ret; for(auto i:dash) ret+=ToString((wisrel?width:1.0)*i/10.0)+"_"; ret.back()=':'; ret+=ToString(shift)+"c"; return ret; } operator bool() const {return !dash.empty();} void Clear() {dash.clear(); shift=0.0; wisrel=false;} bool Convert(const std::string& in, std::string& err, double w=0.0) { Clear(); shift=0; if(0==in.size()) return true; // No dash if(std::string::npos==in.find_first_not_of(".-")) // dot-dash form { width=(0==w)?default_width:w; wisrel=true; for(const auto& i:in) { if('.'==i) dash.push_back(1); // Dot else dash.push_back(8); // Dash dash.push_back(4); // Gap } return true; } std::string dstr=in; // Determine shift { WordList wl=Split(in,":"); if(wl.size()>2) {err="Incorrect dash format"; return false;} if(2==wl.size()) { Value2Width s(wl.back()); bool suc=true; shift=s(&suc,err); if(!suc) return false; // Parse error dstr=wl.front(); } } WordList wl=Split(dstr,"_"); for(const auto& i:wl) { Value2Width d(i); bool suc=true; dash.push_back(d(&suc,err)); if(!suc) return false; // Parse error } return true; } }; // Pen struct gmt_pen: public gmt_struct { double width; struct gmt_color color; struct gmt_dash dash; static const double default_width; std::string Value() const {return ToString(width/10.0)+"c,"+color.Value()+(dash?(","+dash.Value()):"");} // Interpret one numeric argument as width value bool Convert(double in, std::string& err) { Value2Width w(in); bool suc=true; color.Convert(0,err); // Black dash.Clear(); width=w(&suc,err); return suc; } // Convert from string bool Convert(const std::string& istr, std::string& err) { std::string str=istr; WordList::const_iterator ci; tolower(str); WordList wl=Split(str,",",true); // Defaults width=default_width; color.Convert(0,err); // Black dash.Clear(); if(wl.size()>3) return false; // String is [width][,color][,dash] ci=wl.begin(); if(wl.end()!=ci && 0!=ci->size()) { Value2Width w(*ci); bool suc=true; width=w(&suc,err); if(!suc) return false; // Parse error } if(wl.end()!=ci) ci++; if(wl.end()!=ci && 0!=ci->size()) { if(!color.Convert(*ci,err)) return false; // Parse error } if(wl.end()!=ci) ci++; if(wl.end()!=ci && 0!=ci->size()) { if(!dash.Convert(*ci,err,width)) return false; // Parse error } return true; } }; // Font struct gmt_font: public gmt_struct { double size; struct gmt_color color; std::string family; static std::string default_family; static std::set families; static const double default_size; std::string Value() const {return ToString(size)+"p,"+family+","+color.Value();} // Interpret one numeric argument as size of default black font bool Convert(double in, std::string& err) { Value2Width s(in); bool suc=true; color.Convert(0,err); // Black size=s(&suc,err); family=default_family; return suc; } // Convert from string bool Convert(const std::string& str, std::string& err) { WordList::const_iterator ci; WordList wl=Split(str,",",true); std::string fakeerr; // Defaults size=default_size; family=default_family; color.Convert(0,fakeerr); // Black if(wl.size()>3) {err="String "+str+" is not font string"; return false;} // String is [size][,family][,color] or [family][,color] ci=wl.begin(); if(wl.end()!=ci && 0!=ci->size()) { Value2Width s(*ci); bool suc=true; { std::string fake; double newsize=s(&suc,fake); if(suc) size=newsize; } if(!suc) // Parse error. check if argument is font name { if(0==families.count(*ci)) return false; // No, argument is not allowed font name family=*ci; if(wl.size()>2) {err="String "+str+" is not font string"; return false;} // If first word is font name, then words count is 1 or 2. goto read_color; } } if(wl.end()!=ci) ci++; if(wl.end()!=ci && 0!=ci->size()) { if(0==families.count(*ci)) {err="Unknown font family: "+(*ci); return false;} // Argument is not allowed font name family=*ci; } read_color: if(wl.end()!=ci) ci++; if(wl.end()!=ci && 0!=ci->size()) { if(!color.Convert(*ci,err)) return false; // Parse error } return true; } static bool FillFontNames(); }; // Layer struct gmt_layer: public gmt_struct { double shiftx,shifty; struct gmt_projection proj; std::string creator; std::shared_ptr data; std::string Value() const {return creator+(Shifted()?("("+ToString(shiftx)+"x"+ToString(shifty)+")"):"");} bool Shifted() const {return shiftx!=0.0 || shifty!=0.0;} std::string BeginShift() const {return (Shifted()?("V "+ToString(cm2GMT(shiftx))+" "+ToString(cm2GMT(shifty))+" T\n"):"");} std::string EndShift() const {return (Shifted()?"U\n":"");} }; #endif