private/Get-WtwColorCircleEmoji.ps1
|
function Get-WtwColorCircleEmoji { <# .SYNOPSIS Pick the closest Unicode color-circle emoji for a #rrggbb color. .DESCRIPTION Returns a single-codepoint emoji from the standard color-circle set so SourceGit/Superset display reliably without ZWJ/font-composition issues. Distance is Euclidean RGB; saturation acts as a tiebreaker between gray-leaning candidates (black/white) and chromatic ones. #> param([string] $Hex) if ([string]::IsNullOrWhiteSpace($Hex)) { return [char]::ConvertFromUtf32(0x26AA) } # ⚪ default $clean = $Hex.TrimStart('#') if ($clean -notmatch '^[0-9a-fA-F]{6}$') { return [char]::ConvertFromUtf32(0x26AA) } $r = [Convert]::ToInt32($clean.Substring(0, 2), 16) $g = [Convert]::ToInt32($clean.Substring(2, 2), 16) $b = [Convert]::ToInt32($clean.Substring(4, 2), 16) # Representative hex for each circle. Codepoints from Unicode Symbols/Geometric Shapes: # 🔴 1F534, 🟠 1F7E0, 🟡 1F7E1, 🟢 1F7E2, 🔵 1F535, 🟣 1F7E3, 🟤 1F7E4, ⚫ 26AB, ⚪ 26AA $palette = @( @{ CP = 0x1F534; R = 0xE0; G = 0x21; B = 0x21 } # red @{ CP = 0x1F7E0; R = 0xF1; G = 0x8C; B = 0x29 } # orange @{ CP = 0x1F7E1; R = 0xF4; G = 0xD0; B = 0x36 } # yellow @{ CP = 0x1F7E2; R = 0x4B; G = 0xA5; B = 0x32 } # green @{ CP = 0x1F535; R = 0x29; G = 0x6E; B = 0xC8 } # blue @{ CP = 0x1F7E3; R = 0x80; G = 0x40; B = 0xB0 } # purple @{ CP = 0x1F7E4; R = 0x8B; G = 0x4F; B = 0x2D } # brown @{ CP = 0x26AB; R = 0x1F; G = 0x1F; B = 0x1F } # black @{ CP = 0x26AA; R = 0xEE; G = 0xEE; B = 0xEE } # white ) # HSV-style saturation = (max - min) / max. Low-sat inputs (near-gray) should snap # to black/white instead of the closest chromatic circle, which usually looks wrong. $mx = [Math]::Max($r, [Math]::Max($g, $b)) $mn = [Math]::Min($r, [Math]::Min($g, $b)) $sat = if ($mx -eq 0) { 0.0 } else { 1.0 - ($mn / $mx) } if ($sat -lt 0.15) { # Effectively gray — choose black vs white by brightness. return $(if ($mx -lt 128) { [char]::ConvertFromUtf32(0x26AB) } else { [char]::ConvertFromUtf32(0x26AA) }) } $bestCp = 0x26AA $bestDist = [double]::MaxValue foreach ($p in $palette) { if ($p.CP -eq 0x26AB -or $p.CP -eq 0x26AA) { continue } # skip grays for chromatic inputs $dr = $r - $p.R; $dg = $g - $p.G; $db = $b - $p.B $d = [double]($dr * $dr + $dg * $dg + $db * $db) if ($d -lt $bestDist) { $bestDist = $d $bestCp = $p.CP } } return [char]::ConvertFromUtf32($bestCp) } function Format-WtwPrettyNameWithCircle { <# .SYNOPSIS Prepend the color-circle emoji to a pretty name, dedup-safe. .DESCRIPTION If the name already starts with one of our circle emojis we replace it instead of stacking, so repeated renames don't accumulate prefixes. #> param( [Parameter(Mandatory)][string] $Hex, [string] $Name ) $circle = Get-WtwColorCircleEmoji -Hex $Hex if ([string]::IsNullOrWhiteSpace($Name)) { return $circle } $circleCps = @(0x1F534, 0x1F7E0, 0x1F7E1, 0x1F7E2, 0x1F535, 0x1F7E3, 0x1F7E4, 0x26AB, 0x26AA) $stripped = $Name if ($Name.Length -ge 2) { $cp = [Char]::ConvertToUtf32($Name, 0) if ($circleCps -contains $cp) { # surrogate pair (1F5xx / 1F7xx range) → 2 chars; symbols (26AB / 26AA) → 1 char $skip = if ($cp -gt 0xFFFF) { 2 } else { 1 } $stripped = $Name.Substring($skip).TrimStart() } } return "$circle $stripped" } |