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 →