|
|
@ -32,7 +32,7 @@ class GetDouble<Source, Policy, Policies...>: public GetDouble<Source, Policies. |
|
|
|
GetDouble(GetDouble&) = delete; |
|
|
|
GetDouble(GetDouble&) = delete; |
|
|
|
template<class... Args> |
|
|
|
template<class... Args> |
|
|
|
GetDouble(Args... args):GetDouble<Source,Policies...>(args...) {}; |
|
|
|
GetDouble(Args... args):GetDouble<Source,Policies...>(args...) {}; |
|
|
|
double operator()(bool* suc) const {return p(GetDouble<Source,Policies...>::operator()(suc),suc);} |
|
|
|
double operator()(bool* suc, std::string& err) const {return p(GetDouble<Source,Policies...>::operator()(suc,err),suc,err);} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Bottom of recursion
|
|
|
|
// Bottom of recursion
|
|
|
@ -45,37 +45,57 @@ class GetDouble<Source>: public Source |
|
|
|
GetDouble() = delete; |
|
|
|
GetDouble() = delete; |
|
|
|
template<class... Args> |
|
|
|
template<class... Args> |
|
|
|
GetDouble(Args... args):Source(args...) {}; |
|
|
|
GetDouble(Args... args):Source(args...) {}; |
|
|
|
double operator()(bool* suc) const {return Source::operator()(suc);} |
|
|
|
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
|
|
|
|
// 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
|
|
|
|
// Policy to check if value is greater or equal num/denum
|
|
|
|
template<int32_t num, uint32_t denum=1> |
|
|
|
template<int32_t num, uint32_t denum=1, bool equal=true> |
|
|
|
class PMin |
|
|
|
class PMin |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
static bool Compare(double d1, double d2) {return equal?(d1<=d2):(d1<d2);} |
|
|
|
|
|
|
|
|
|
|
|
public: |
|
|
|
public: |
|
|
|
double operator()(double v, bool* suc) const {if(v<static_cast<double>(num)/denum) *suc=false; return v;} |
|
|
|
double operator()(double v, bool* suc, std::string& err) const |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if(Compare(v,static_cast<double>(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
|
|
|
|
// Policy to check if value is lesser or equal num/denum
|
|
|
|
template<int32_t num, uint32_t denum=1> |
|
|
|
template<int32_t num, uint32_t denum=1, bool equal=true> |
|
|
|
class PMax |
|
|
|
class PMax |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
static bool Compare(double d1, double d2) {return equal?(d1>=d2):(d1>d2);} |
|
|
|
|
|
|
|
|
|
|
|
public: |
|
|
|
public: |
|
|
|
double operator()(double v, bool* suc) const {if(v>static_cast<double>(num)/denum) *suc=false; return v;} |
|
|
|
double operator()(double v, bool* suc, std::string& err) const |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if(Compare(v,static_cast<double>(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
|
|
|
|
// Get double value from string or number
|
|
|
|
class SourceValue |
|
|
|
class SourceValue |
|
|
|
{ |
|
|
|
{ |
|
|
|
double d; |
|
|
|
double d; |
|
|
|
bool ok; |
|
|
|
std::string err; |
|
|
|
public: |
|
|
|
public: |
|
|
|
SourceValue(const std::string& s) {ok=str2double(s,&d);} |
|
|
|
SourceValue(const std::string& s) {if(!str2double(s,&d)) err="Can't convert string \""+s+"\" to double value";} |
|
|
|
SourceValue(double s):d(s),ok(true) {} |
|
|
|
SourceValue(double s):d(s) {} |
|
|
|
double operator()(bool* suc) const |
|
|
|
double operator()(bool* suc, std::string& ierr) const |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(!ok) *suc=false; |
|
|
|
if(!err.empty()) {*suc=false; ierr=err;} |
|
|
|
return d; |
|
|
|
return d; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
@ -83,7 +103,7 @@ class SourceValue |
|
|
|
// Helper types for colors
|
|
|
|
// Helper types for colors
|
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<255> > Value2RGB; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<255> > Value2RGB; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<360> > Value2Hue; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<360> > Value2Hue; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<1> > Value2SV; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax< 1 > > Value2SV; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<100> > Value2CMYK; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<100> > Value2CMYK; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<100> > Value2Transp; |
|
|
|
typedef GetDouble<SourceValue,PMin<0>,PMax<100> > Value2Transp; |
|
|
|
// Helper type for pens and dashes
|
|
|
|
// Helper type for pens and dashes
|
|
|
@ -546,17 +566,17 @@ struct gmt_color: public gmt_struct |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Interpret one numeric argument as gray value
|
|
|
|
// Interpret one numeric argument as gray value
|
|
|
|
bool Convert(double gr) |
|
|
|
bool Convert(double gr, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2RGB g(gr); |
|
|
|
Value2RGB g(gr); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
model=GRAY; |
|
|
|
model=GRAY; |
|
|
|
transparency=0.0; |
|
|
|
transparency=0.0; |
|
|
|
gray=g(&suc); |
|
|
|
gray=g(&suc,err); |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool Convert(const std::string& istr) |
|
|
|
bool Convert(const std::string& istr, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
std::string cstr=istr; |
|
|
|
std::string cstr=istr; |
|
|
|
tolower(cstr); |
|
|
|
tolower(cstr); |
|
|
@ -573,7 +593,7 @@ struct gmt_color: public gmt_struct |
|
|
|
cstr=*ci; |
|
|
|
cstr=*ci; |
|
|
|
ci++; |
|
|
|
ci++; |
|
|
|
Value2Transp t(*ci); |
|
|
|
Value2Transp t(*ci); |
|
|
|
transparency=t(&suc); |
|
|
|
transparency=t(&suc,err); |
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -581,9 +601,9 @@ struct gmt_color: public gmt_struct |
|
|
|
WordList wl_slash=Split(cstr,"/"),wl_hyphen=Split(cstr,"-"); |
|
|
|
WordList wl_slash=Split(cstr,"/"),wl_hyphen=Split(cstr,"-"); |
|
|
|
WordList::size_type slash_size=wl_slash.size(),hyphen_size=wl_hyphen.size(); |
|
|
|
WordList::size_type slash_size=wl_slash.size(),hyphen_size=wl_hyphen.size(); |
|
|
|
// Checks
|
|
|
|
// Checks
|
|
|
|
if(slash_size>1 && hyphen_size>1) return false; // Delimiter can be "/" or "-", not both
|
|
|
|
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) return false; // Size can be 1 or 3 for rgb or 4 for cmyk
|
|
|
|
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) return false; // Size can be 1 or 3 for hsv
|
|
|
|
if(2==hyphen_size || hyphen_size>3) {err="There is 3 color components in HSV model"; return false;} |
|
|
|
// Gray or name
|
|
|
|
// Gray or name
|
|
|
|
// TODO: Hex representation
|
|
|
|
// TODO: Hex representation
|
|
|
|
if(1==slash_size && 1==hyphen_size) |
|
|
|
if(1==slash_size && 1==hyphen_size) |
|
|
@ -592,7 +612,8 @@ struct gmt_color: public gmt_struct |
|
|
|
Value2RGB g(cstr); |
|
|
|
Value2RGB g(cstr); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
model=GRAY; |
|
|
|
model=GRAY; |
|
|
|
gray=g(&suc); |
|
|
|
gray=g(&suc,err); |
|
|
|
|
|
|
|
err="Can't understand string "+cstr+", this is nor color name, nor gray value (0-255)"; |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
// RGB
|
|
|
|
// RGB
|
|
|
@ -603,9 +624,9 @@ struct gmt_color: public gmt_struct |
|
|
|
Value2RGB blue(wl_slash.front()); |
|
|
|
Value2RGB blue(wl_slash.front()); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
model=RGB; |
|
|
|
model=RGB; |
|
|
|
r=red(&suc); |
|
|
|
r=red(&suc,err); if(!suc) return suc; |
|
|
|
g=green(&suc); |
|
|
|
g=green(&suc,err);if(!suc) return suc; |
|
|
|
b=blue(&suc); |
|
|
|
b=blue(&suc,err); |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
// HSV
|
|
|
|
// HSV
|
|
|
@ -616,9 +637,9 @@ struct gmt_color: public gmt_struct |
|
|
|
Value2SV v(wl_hyphen.front()); |
|
|
|
Value2SV v(wl_hyphen.front()); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
model=HSV; |
|
|
|
model=HSV; |
|
|
|
hue=h(&suc); |
|
|
|
hue=h(&suc,err); if(!suc) return suc; |
|
|
|
saturation=s(&suc); |
|
|
|
saturation=s(&suc,err);if(!suc) return suc; |
|
|
|
value=v(&suc); |
|
|
|
value=v(&suc,err); |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
// CMYK
|
|
|
|
// CMYK
|
|
|
@ -630,10 +651,10 @@ struct gmt_color: public gmt_struct |
|
|
|
Value2CMYK k(wl_slash.front()); |
|
|
|
Value2CMYK k(wl_slash.front()); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
model=CMYK; |
|
|
|
model=CMYK; |
|
|
|
cyan=c(&suc); |
|
|
|
cyan=c(&suc,err); if(!suc) return suc; |
|
|
|
magenta=m(&suc); |
|
|
|
magenta=m(&suc,err);if(!suc) return suc; |
|
|
|
yellow=y(&suc); |
|
|
|
yellow=y(&suc,err); if(!suc) return suc; |
|
|
|
black=k(&suc); |
|
|
|
black=k(&suc,err); |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -678,7 +699,7 @@ struct gmt_dash: public gmt_struct |
|
|
|
operator bool() const {return !dash.empty();} |
|
|
|
operator bool() const {return !dash.empty();} |
|
|
|
void Clear() {dash.clear(); shift=0.0; wisrel=false;} |
|
|
|
void Clear() {dash.clear(); shift=0.0; wisrel=false;} |
|
|
|
|
|
|
|
|
|
|
|
bool Convert(const std::string& in, double w=0.0) |
|
|
|
bool Convert(const std::string& in, std::string& err, double w=0.0) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Clear(); |
|
|
|
Clear(); |
|
|
|
shift=0; |
|
|
|
shift=0; |
|
|
@ -704,7 +725,7 @@ struct gmt_dash: public gmt_struct |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width s(wl.back()); |
|
|
|
Value2Width s(wl.back()); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
shift=s(&suc); |
|
|
|
shift=s(&suc,err); |
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
dstr=wl.front(); |
|
|
|
dstr=wl.front(); |
|
|
|
} |
|
|
|
} |
|
|
@ -714,7 +735,7 @@ struct gmt_dash: public gmt_struct |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width d(i); |
|
|
|
Value2Width d(i); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
dash.push_back(d(&suc)); |
|
|
|
dash.push_back(d(&suc,err)); |
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
@ -732,18 +753,18 @@ struct gmt_pen: public gmt_struct |
|
|
|
std::string Value() const {return ToString(width/10.0)+"c,"+color.Value()+(dash?(","+dash.Value()):"");} |
|
|
|
std::string Value() const {return ToString(width/10.0)+"c,"+color.Value()+(dash?(","+dash.Value()):"");} |
|
|
|
|
|
|
|
|
|
|
|
// Interpret one numeric argument as width value
|
|
|
|
// Interpret one numeric argument as width value
|
|
|
|
bool Convert(double in) |
|
|
|
bool Convert(double in, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width w(in); |
|
|
|
Value2Width w(in); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
color.Convert(0); // Black
|
|
|
|
color.Convert(0,err); // Black
|
|
|
|
dash.Clear(); |
|
|
|
dash.Clear(); |
|
|
|
width=w(&suc); |
|
|
|
width=w(&suc,err); |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Convert from string
|
|
|
|
// Convert from string
|
|
|
|
bool Convert(const std::string& istr) |
|
|
|
bool Convert(const std::string& istr, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
std::string str=istr; |
|
|
|
std::string str=istr; |
|
|
|
WordList::const_iterator ci; |
|
|
|
WordList::const_iterator ci; |
|
|
@ -752,7 +773,7 @@ struct gmt_pen: public gmt_struct |
|
|
|
|
|
|
|
|
|
|
|
// Defaults
|
|
|
|
// Defaults
|
|
|
|
width=default_width; |
|
|
|
width=default_width; |
|
|
|
color.Convert(0); // Black
|
|
|
|
color.Convert(0,err); // Black
|
|
|
|
dash.Clear(); |
|
|
|
dash.Clear(); |
|
|
|
|
|
|
|
|
|
|
|
if(wl.size()>3) return false; // String is [width][,color][,dash]
|
|
|
|
if(wl.size()>3) return false; // String is [width][,color][,dash]
|
|
|
@ -761,18 +782,18 @@ struct gmt_pen: public gmt_struct |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width w(*ci); |
|
|
|
Value2Width w(*ci); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
width=w(&suc); |
|
|
|
width=w(&suc,err); |
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
if(!suc) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(!color.Convert(*ci)) return false; // Parse error
|
|
|
|
if(!color.Convert(*ci,err)) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(!dash.Convert(*ci,width)) return false; // Parse error
|
|
|
|
if(!dash.Convert(*ci,err,width)) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
@ -792,18 +813,18 @@ struct gmt_font: public gmt_struct |
|
|
|
std::string Value() const {return ToString(size)+"p,"+family+","+color.Value();} |
|
|
|
std::string Value() const {return ToString(size)+"p,"+family+","+color.Value();} |
|
|
|
|
|
|
|
|
|
|
|
// Interpret one numeric argument as size of default black font
|
|
|
|
// Interpret one numeric argument as size of default black font
|
|
|
|
bool Convert(double in) |
|
|
|
bool Convert(double in, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width s(in); |
|
|
|
Value2Width s(in); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
color.Convert(0); // Black
|
|
|
|
color.Convert(0,err); // Black
|
|
|
|
size=s(&suc); |
|
|
|
size=s(&suc,err); |
|
|
|
family=default_family; |
|
|
|
family=default_family; |
|
|
|
return suc; |
|
|
|
return suc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Convert from string
|
|
|
|
// Convert from string
|
|
|
|
bool Convert(const std::string& str) |
|
|
|
bool Convert(const std::string& str, std::string& err) |
|
|
|
{ |
|
|
|
{ |
|
|
|
WordList::const_iterator ci; |
|
|
|
WordList::const_iterator ci; |
|
|
|
WordList wl=Split(str,",",true); |
|
|
|
WordList wl=Split(str,",",true); |
|
|
@ -811,34 +832,34 @@ struct gmt_font: public gmt_struct |
|
|
|
// Defaults
|
|
|
|
// Defaults
|
|
|
|
size=default_size; |
|
|
|
size=default_size; |
|
|
|
family=default_family; |
|
|
|
family=default_family; |
|
|
|
color.Convert(0); // Black
|
|
|
|
color.Convert(0,err); // Black
|
|
|
|
|
|
|
|
|
|
|
|
if(wl.size()>3) return false; // String is [size][,family][,color] or [family][,color]
|
|
|
|
if(wl.size()>3) {err="String "+str+" is not font string"; return false;} // String is [size][,family][,color] or [family][,color]
|
|
|
|
ci=wl.begin(); |
|
|
|
ci=wl.begin(); |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Value2Width s(*ci); |
|
|
|
Value2Width s(*ci); |
|
|
|
bool suc=true; |
|
|
|
bool suc=true; |
|
|
|
size=s(&suc); |
|
|
|
{std::string fake; size=s(&suc,fake);} |
|
|
|
if(!suc) // Parse error. check if argument is font name
|
|
|
|
if(!suc) // Parse error. check if argument is font name
|
|
|
|
{ |
|
|
|
{ |
|
|
|
if(0==families.count(*ci)) return false; // No, argument is not allowed font name
|
|
|
|
if(0==families.count(*ci)) return false; // No, argument is not allowed font name
|
|
|
|
family=*ci; |
|
|
|
family=*ci; |
|
|
|
if(wl.size()>2) return false; // If first word is font name, then words count is 1 or 2.
|
|
|
|
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; |
|
|
|
goto read_color; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(0==families.count(*ci)) return false; // Argument is not allowed font name
|
|
|
|
if(0==families.count(*ci)) {err="Unknown font family: "+(*ci); return false;} // Argument is not allowed font name
|
|
|
|
family=*ci; |
|
|
|
family=*ci; |
|
|
|
} |
|
|
|
} |
|
|
|
read_color: |
|
|
|
read_color: |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci) ci++; |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
if(wl.end()!=ci && 0!=ci->size()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(!color.Convert(*ci)) return false; // Parse error
|
|
|
|
if(!color.Convert(*ci,err)) return false; // Parse error
|
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|