Skip to content

Box-Shadow Generator — C++ source

Design layered CSS box-shadows with offset, blur, spread, color, and inset. Live preview and copy-ready CSS.

This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.

// box-shadow-generator — C++ polyglot showcase port.
//
// Pure CSS box-shadow builder. Formats one or more shadow layers and joins
// them into a single CSS box-shadow value. Deterministic, never throws on
// bad input: invalid colors quietly fall back to a neutral translucent
// black, so a single bad color never breaks the whole stack.
//
// This is the C++ sibling of src/lib/boxShadow.ts (the canonical TypeScript
// that powers the live tool). The public surface mirrors the TS: a
// ShadowLayer struct plus parse_color, format_layer, and build_box_shadow.
// The four tiny color-shape checks are hand-rolled like the Rust sibling
// rather than pulling in <regex>, which is heavyweight for this job — they
// accept exactly what the TS regexes do, nothing more.
//
// Language: C++17 (standard library only).
// Ported from src/lib/boxShadow.ts.
// Source: CosmoDev polyglot showcase port.
// License: display source — part of CosmoDev's polyglot tool pages.

#include <algorithm>
#include <cctype>
#include <cmath>
#include <initializer_list>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

/// A single layer in a CSS box-shadow stack.
struct ShadowLayer {
    bool inset = false;   // draw the shadow inside the box
    double offset_x = 0;  // horizontal offset in px
    double offset_y = 0;  // vertical offset in px
    double blur = 0;      // blur radius in px
    double spread = 0;    // spread distance in px
    std::string color;    // any CSS color (named, hex, rgb(), hsl(), ...)
};

/// Outcome of validating a color string. `error` is std::nullopt when `ok`
/// is true, mirroring the TypeScript `{ ok: boolean; error: string | null }`
/// shape.
struct ColorResult {
    bool ok;
    std::optional<std::string> error;
};

namespace {

// CSS named colors accepted without further inspection. parse_color()
// lower-cases its input first, so the comparison is effectively
// case-insensitive.
const std::vector<std::string> named_colors = {
    "transparent", "black", "white", "red", "green", "blue", "yellow",
    "orange", "purple", "pink", "gray", "grey", "brown", "cyan", "magenta",
};

bool is_named_color(const std::string& c) {
    return std::find(named_colors.begin(), named_colors.end(), c) != named_colors.end();
}

// Matches the TS `^#[0-9a-f]{3}([0-9a-f]{3})?$` and `^#[0-9a-f]{8}$` rules:
// a leading '#' followed by exactly 3, 6, or 8 lower-case hex digits.
// `s` must already be lower-cased, as in the TS original.
bool is_hex_color(const std::string& s) {
    if (s.empty() || s[0] != '#') return false;
    const size_t n = s.size() - 1;
    if (n != 3 && n != 6 && n != 8) return false;
    for (size_t i = 1; i < s.size(); ++i) {
        const char c = s[i];
        const bool digit = std::isdigit(static_cast<unsigned char>(c)) != 0;
        const bool lower_hex = c >= 'a' && c <= 'f';
        if (!digit && !lower_hex) return false;
    }
    return true;
}

// Matches the TS `^rgba?\([^)]+\)$` / `^hsla?\([^)]+\)$` rules: a function
// name, an opening paren, one or more characters that are not ')', and a
// closing paren at the very end. The inner contents are otherwise unchecked,
// matching the live tool's permissive behavior. `prefixes` should list the
// longer form first (e.g. "rgba" before "rgb") so each is tried
// independently.
bool is_function_color(const std::string& s, std::initializer_list<const char*> prefixes) {
    for (const char* prefix : prefixes) {
        const std::string p(prefix);
        if (s.compare(0, p.size(), p) != 0) continue;
        const std::string rest = s.substr(p.size());
        if (rest.size() >= 3 && rest.front() == '(' && rest.back() == ')') {
            const std::string inside = rest.substr(1, rest.size() - 2);
            if (!inside.empty() && inside.find(')') == std::string::npos) {
                return true;
            }
        }
    }
    return false;
}

// Trim ASCII whitespace from both ends — the `.trim()` the TS applies
// before validating and when echoing a color back.
std::string trim(const std::string& s) {
    const char* ws = " \t\n\r\f\v";
    const auto begin = s.find_first_not_of(ws);
    if (begin == std::string::npos) return "";
    const auto end = s.find_last_not_of(ws);
    return s.substr(begin, end - begin + 1);
}

std::string to_lower(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
        return static_cast<char>(std::tolower(c));
    });
    return s;
}

}  // namespace

/// Validate a CSS color string. Accepts the curated named-color set plus
/// hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba(), and hsl()/hsla() forms.
/// The functional notations are checked for well-formed wrappers only, not
/// their numeric contents. Input is trimmed and lower-cased before
/// matching, exactly like the TS original.
ColorResult parse_color(const std::string& color) {
    // Lower-case + trim once so every shape check below sees a canonical form.
    const std::string c = to_lower(trim(color));
    if (c.empty()) return ColorResult{false, std::string("empty color")};
    if (is_named_color(c)) return ColorResult{true, std::nullopt};
    if (is_hex_color(c)) return ColorResult{true, std::nullopt};
    if (is_function_color(c, {"rgba", "rgb"}) || is_function_color(c, {"hsla", "hsl"})) {
        return ColorResult{true, std::nullopt};
    }
    return ColorResult{false, "invalid color: " + color};
}

namespace {

// Keep a color when it parses and otherwise substitute a neutral
// translucent black. This is what makes build_box_shadow total over
// arbitrary input.
std::string normalize_color(const std::string& color) {
    return parse_color(color).ok ? trim(color) : "rgba(0,0,0,0.5)";
}

// Render a double the way JavaScript's template literals would: whole
// numbers drop the decimal point (5, not 5.0); the default stream format
// keeps the short form for fractions (5.5 -> "5.5").
std::string format_number(double v) {
    std::ostringstream out;
    if (v == std::floor(v) && std::fabs(v) < 1e15) {
        out << static_cast<long long>(v);
    } else {
        out << v;
    }
    return out.str();
}

}  // namespace

/// Render one shadow layer as its CSS fragment, e.g.
/// "inset 4px 8px 16px 0px #1a2b3c" or "0px 2px 4px 0px rgba(0,0,0,0.5)".
std::string format_layer(const ShadowLayer& layer) {
    std::ostringstream out;
    if (layer.inset) out << "inset ";
    out << format_number(layer.offset_x) << "px "
        << format_number(layer.offset_y) << "px "
        << format_number(layer.blur) << "px "
        << format_number(layer.spread) << "px "
        << normalize_color(layer.color);
    return out.str();
}

/// Compose a full CSS box-shadow declaration from an ordered list of layers
/// (the first layer renders on top). An empty list yields the CSS keyword
/// "none", matching the property's default value.
std::string build_box_shadow(const std::vector<ShadowLayer>& layers) {
    if (layers.empty()) return "none";
    std::string out;
    for (size_t i = 0; i < layers.size(); ++i) {
        if (i > 0) out += ", ";
        out += format_layer(layers[i]);
    }
    return out;
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →