// sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" #include #include "ast.hpp" #include "fn_utils.hpp" #include "fn_colors.hpp" #include "util.hpp" #include "util_string.hpp" namespace Sass { namespace Functions { bool string_argument(AST_Node_Obj obj) { String_Constant* s = Cast(obj); if (s == nullptr) return false; const sass::string& str = s->value(); return starts_with(str, "calc(") || starts_with(str, "var("); } void hsla_alpha_percent_deprecation(const SourceSpan& pstate, const sass::string val) { sass::string msg("Passing a percentage as the alpha value to hsla() will be interpreted"); sass::string tail("differently in future versions of Sass. For now, use " + val + " instead."); deprecated(msg, tail, false, pstate); } Signature rgb_sig = "rgb($red, $green, $blue)"; BUILT_IN(rgb) { if ( string_argument(env["$red"]) || string_argument(env["$green"]) || string_argument(env["$blue"]) ) { return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" + env["$red"]->to_string() + ", " + env["$green"]->to_string() + ", " + env["$blue"]->to_string() + ")" ); } return SASS_MEMORY_NEW(Color_RGBA, pstate, COLOR_NUM("$red"), COLOR_NUM("$green"), COLOR_NUM("$blue")); } Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; BUILT_IN(rgba_4) { if ( string_argument(env["$red"]) || string_argument(env["$green"]) || string_argument(env["$blue"]) || string_argument(env["$alpha"]) ) { return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + env["$red"]->to_string() + ", " + env["$green"]->to_string() + ", " + env["$blue"]->to_string() + ", " + env["$alpha"]->to_string() + ")" ); } return SASS_MEMORY_NEW(Color_RGBA, pstate, COLOR_NUM("$red"), COLOR_NUM("$green"), COLOR_NUM("$blue"), ALPHA_NUM("$alpha")); } Signature rgba_2_sig = "rgba($color, $alpha)"; BUILT_IN(rgba_2) { if ( string_argument(env["$color"]) ) { return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + env["$color"]->to_string() + ", " + env["$alpha"]->to_string() + ")" ); } Color_RGBA_Obj c_arg = ARG("$color", Color)->toRGBA(); if ( string_argument(env["$alpha"]) ) { sass::ostream strm; strm << "rgba(" << (int)c_arg->r() << ", " << (int)c_arg->g() << ", " << (int)c_arg->b() << ", " << env["$alpha"]->to_string() << ")"; return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); } Color_RGBA_Obj new_c = SASS_MEMORY_COPY(c_arg); new_c->a(ALPHA_NUM("$alpha")); new_c->disp(""); return new_c.detach(); } //////////////// // RGB FUNCTIONS //////////////// Signature red_sig = "red($color)"; BUILT_IN(red) { Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); return SASS_MEMORY_NEW(Number, pstate, color->r()); } Signature green_sig = "green($color)"; BUILT_IN(green) { Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); return SASS_MEMORY_NEW(Number, pstate, color->g()); } Signature blue_sig = "blue($color)"; BUILT_IN(blue) { Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); return SASS_MEMORY_NEW(Number, pstate, color->b()); } Color_RGBA* colormix(Context& ctx, SourceSpan& pstate, Color* color1, Color* color2, double weight) { Color_RGBA_Obj c1 = color1->toRGBA(); Color_RGBA_Obj c2 = color2->toRGBA(); double p = weight/100; double w = 2*p - 1; double a = c1->a() - c2->a(); double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; double w2 = 1 - w1; return SASS_MEMORY_NEW(Color_RGBA, pstate, Sass::round(w1*c1->r() + w2*c2->r(), ctx.c_options.precision), Sass::round(w1*c1->g() + w2*c2->g(), ctx.c_options.precision), Sass::round(w1*c1->b() + w2*c2->b(), ctx.c_options.precision), c1->a()*p + c2->a()*(1-p)); } Signature mix_sig = "mix($color1, $color2, $weight: 50%)"; BUILT_IN(mix) { Color_Obj color1 = ARG("$color1", Color); Color_Obj color2 = ARG("$color2", Color); double weight = DARG_U_PRCT("$weight"); return colormix(ctx, pstate, color1, color2, weight); } //////////////// // HSL FUNCTIONS //////////////// Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; BUILT_IN(hsl) { if ( string_argument(env["$hue"]) || string_argument(env["$saturation"]) || string_argument(env["$lightness"]) ) { return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" + env["$hue"]->to_string() + ", " + env["$saturation"]->to_string() + ", " + env["$lightness"]->to_string() + ")" ); } return SASS_MEMORY_NEW(Color_HSLA, pstate, ARGVAL("$hue"), ARGVAL("$saturation"), ARGVAL("$lightness"), 1.0); } Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; BUILT_IN(hsla) { if ( string_argument(env["$hue"]) || string_argument(env["$saturation"]) || string_argument(env["$lightness"]) || string_argument(env["$alpha"]) ) { return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" + env["$hue"]->to_string() + ", " + env["$saturation"]->to_string() + ", " + env["$lightness"]->to_string() + ", " + env["$alpha"]->to_string() + ")" ); } Number* alpha = ARG("$alpha", Number); if (alpha && alpha->unit() == "%") { Number_Obj val = SASS_MEMORY_COPY(alpha); val->numerators.clear(); // convert val->value(val->value() / 100.0); sass::string nr(val->to_string(ctx.c_options)); hsla_alpha_percent_deprecation(pstate, nr); } return SASS_MEMORY_NEW(Color_HSLA, pstate, ARGVAL("$hue"), ARGVAL("$saturation"), ARGVAL("$lightness"), ARGVAL("$alpha")); } ///////////////////////////////////////////////////////////////////////// // Query functions ///////////////////////////////////////////////////////////////////////// Signature hue_sig = "hue($color)"; BUILT_IN(hue) { Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); return SASS_MEMORY_NEW(Number, pstate, col->h(), "deg"); } Signature saturation_sig = "saturation($color)"; BUILT_IN(saturation) { Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); return SASS_MEMORY_NEW(Number, pstate, col->s(), "%"); } Signature lightness_sig = "lightness($color)"; BUILT_IN(lightness) { Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); return SASS_MEMORY_NEW(Number, pstate, col->l(), "%"); } ///////////////////////////////////////////////////////////////////////// // HSL manipulation functions ///////////////////////////////////////////////////////////////////////// Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; BUILT_IN(adjust_hue) { Color* col = ARG("$color", Color); double degrees = ARGVAL("$degrees"); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->h(absmod(copy->h() + degrees, 360.0)); return copy.detach(); } Signature lighten_sig = "lighten($color, $amount)"; BUILT_IN(lighten) { Color* col = ARG("$color", Color); double amount = DARG_U_PRCT("$amount"); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->l(clip(copy->l() + amount, 0.0, 100.0)); return copy.detach(); } Signature darken_sig = "darken($color, $amount)"; BUILT_IN(darken) { Color* col = ARG("$color", Color); double amount = DARG_U_PRCT("$amount"); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->l(clip(copy->l() - amount, 0.0, 100.0)); return copy.detach(); } Signature saturate_sig = "saturate($color, $amount: false)"; BUILT_IN(saturate) { // CSS3 filter function overload: pass literal through directly if (!Cast(env["$amount"])) { return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); } Color* col = ARG("$color", Color); double amount = DARG_U_PRCT("$amount"); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->s(clip(copy->s() + amount, 0.0, 100.0)); return copy.detach(); } Signature desaturate_sig = "desaturate($color, $amount)"; BUILT_IN(desaturate) { Color* col = ARG("$color", Color); double amount = DARG_U_PRCT("$amount"); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->s(clip(copy->s() - amount, 0.0, 100.0)); return copy.detach(); } Signature grayscale_sig = "grayscale($color)"; BUILT_IN(grayscale) { // CSS3 filter function overload: pass literal through directly Number* amount = Cast(env["$color"]); if (amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); } Color* col = ARG("$color", Color); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->s(0.0); // just reset saturation return copy.detach(); } ///////////////////////////////////////////////////////////////////////// // Misc manipulation functions ///////////////////////////////////////////////////////////////////////// Signature complement_sig = "complement($color)"; BUILT_IN(complement) { Color* col = ARG("$color", Color); Color_HSLA_Obj copy = col->copyAsHSLA(); copy->h(absmod(copy->h() - 180.0, 360.0)); return copy.detach(); } Signature invert_sig = "invert($color, $weight: 100%)"; BUILT_IN(invert) { // CSS3 filter function overload: pass literal through directly Number* amount = Cast(env["$color"]); double weight = DARG_U_PRCT("$weight"); if (amount) { // TODO: does not throw on 100% manually passed as value if (weight < 100.0) { error("Only one argument may be passed to the plain-CSS invert() function.", pstate, traces); } return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); } Color* col = ARG("$color", Color); Color_RGBA_Obj inv = col->copyAsRGBA(); inv->r(clip(255.0 - inv->r(), 0.0, 255.0)); inv->g(clip(255.0 - inv->g(), 0.0, 255.0)); inv->b(clip(255.0 - inv->b(), 0.0, 255.0)); return colormix(ctx, pstate, inv, col, weight); } ///////////////////////////////////////////////////////////////////////// // Opacity functions ///////////////////////////////////////////////////////////////////////// Signature alpha_sig = "alpha($color)"; Signature opacity_sig = "opacity($color)"; BUILT_IN(alpha) { String_Constant* ie_kwd = Cast(env["$color"]); if (ie_kwd) { return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); } // CSS3 filter function overload: pass literal through directly Number* amount = Cast(env["$color"]); if (amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); } return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); } Signature opacify_sig = "opacify($color, $amount)"; Signature fade_in_sig = "fade-in($color, $amount)"; BUILT_IN(opacify) { Color* col = ARG("$color", Color); double amount = DARG_U_FACT("$amount"); Color_Obj copy = SASS_MEMORY_COPY(col); copy->a(clip(col->a() + amount, 0.0, 1.0)); return copy.detach(); } Signature transparentize_sig = "transparentize($color, $amount)"; Signature fade_out_sig = "fade-out($color, $amount)"; BUILT_IN(transparentize) { Color* col = ARG("$color", Color); double amount = DARG_U_FACT("$amount"); Color_Obj copy = SASS_MEMORY_COPY(col); copy->a(std::max(col->a() - amount, 0.0)); return copy.detach(); } //////////////////////// // OTHER COLOR FUNCTIONS //////////////////////// Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; BUILT_IN(adjust_color) { Color* col = ARG("$color", Color); Number* r = Cast(env["$red"]); Number* g = Cast(env["$green"]); Number* b = Cast(env["$blue"]); Number* h = Cast(env["$hue"]); Number* s = Cast(env["$saturation"]); Number* l = Cast(env["$lightness"]); Number* a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; if (rgb && hsl) { error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); } else if (rgb) { Color_RGBA_Obj c = col->copyAsRGBA(); if (r) c->r(c->r() + DARG_R_BYTE("$red")); if (g) c->g(c->g() + DARG_R_BYTE("$green")); if (b) c->b(c->b() + DARG_R_BYTE("$blue")); if (a) c->a(c->a() + DARG_R_FACT("$alpha")); return c.detach(); } else if (hsl) { Color_HSLA_Obj c = col->copyAsHSLA(); if (h) c->h(c->h() + absmod(h->value(), 360.0)); if (s) c->s(c->s() + DARG_R_PRCT("$saturation")); if (l) c->l(c->l() + DARG_R_PRCT("$lightness")); if (a) c->a(c->a() + DARG_R_FACT("$alpha")); return c.detach(); } else if (a) { Color_Obj c = SASS_MEMORY_COPY(col); c->a(c->a() + DARG_R_FACT("$alpha")); c->a(clip(c->a(), 0.0, 1.0)); return c.detach(); } error("not enough arguments for `adjust-color'", pstate, traces); // unreachable return col; } Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; BUILT_IN(scale_color) { Color* col = ARG("$color", Color); Number* r = Cast(env["$red"]); Number* g = Cast(env["$green"]); Number* b = Cast(env["$blue"]); Number* h = Cast(env["$hue"]); Number* s = Cast(env["$saturation"]); Number* l = Cast(env["$lightness"]); Number* a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; if (rgb && hsl) { error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); } else if (rgb) { Color_RGBA_Obj c = col->copyAsRGBA(); double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; if (rscale) c->r(c->r() + rscale * (rscale > 0.0 ? 255.0 - c->r() : c->r())); if (gscale) c->g(c->g() + gscale * (gscale > 0.0 ? 255.0 - c->g() : c->g())); if (bscale) c->b(c->b() + bscale * (bscale > 0.0 ? 255.0 - c->b() : c->b())); if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); return c.detach(); } else if (hsl) { Color_HSLA_Obj c = col->copyAsHSLA(); double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; if (hscale) c->h(c->h() + hscale * (hscale > 0.0 ? 360.0 - c->h() : c->h())); if (sscale) c->s(c->s() + sscale * (sscale > 0.0 ? 100.0 - c->s() : c->s())); if (lscale) c->l(c->l() + lscale * (lscale > 0.0 ? 100.0 - c->l() : c->l())); if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); return c.detach(); } else if (a) { Color_Obj c = SASS_MEMORY_COPY(col); double ascale = DARG_R_PRCT("$alpha") / 100.0; c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); c->a(clip(c->a(), 0.0, 1.0)); return c.detach(); } error("not enough arguments for `scale-color'", pstate, traces); // unreachable return col; } Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; BUILT_IN(change_color) { Color* col = ARG("$color", Color); Number* r = Cast(env["$red"]); Number* g = Cast(env["$green"]); Number* b = Cast(env["$blue"]); Number* h = Cast(env["$hue"]); Number* s = Cast(env["$saturation"]); Number* l = Cast(env["$lightness"]); Number* a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; if (rgb && hsl) { error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); } else if (rgb) { Color_RGBA_Obj c = col->copyAsRGBA(); if (r) c->r(DARG_U_BYTE("$red")); if (g) c->g(DARG_U_BYTE("$green")); if (b) c->b(DARG_U_BYTE("$blue")); if (a) c->a(DARG_U_FACT("$alpha")); return c.detach(); } else if (hsl) { Color_HSLA_Obj c = col->copyAsHSLA(); if (h) c->h(absmod(h->value(), 360.0)); if (s) c->s(DARG_U_PRCT("$saturation")); if (l) c->l(DARG_U_PRCT("$lightness")); if (a) c->a(DARG_U_FACT("$alpha")); return c.detach(); } else if (a) { Color_Obj c = SASS_MEMORY_COPY(col); c->a(clip(DARG_U_FACT("$alpha"), 0.0, 1.0)); return c.detach(); } error("not enough arguments for `change-color'", pstate, traces); // unreachable return col; } Signature ie_hex_str_sig = "ie-hex-str($color)"; BUILT_IN(ie_hex_str) { Color* col = ARG("$color", Color); Color_RGBA_Obj c = col->toRGBA(); double r = clip(c->r(), 0.0, 255.0); double g = clip(c->g(), 0.0, 255.0); double b = clip(c->b(), 0.0, 255.0); double a = clip(c->a(), 0.0, 1.0) * 255.0; sass::ostream ss; ss << '#' << std::setw(2) << std::setfill('0'); ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); sass::string result = ss.str(); Util::ascii_str_toupper(&result); return SASS_MEMORY_NEW(String_Quoted, pstate, result); } } }