Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/tui/meter.go

135 lines
3.4 KiB
Go

/*
©AngelaMos | 2026
meter.go
The spectral capacity meter: payload-vs-carrier fill for hide, comparative table for capacity
*/
package tui
import (
"fmt"
"math"
"strings"
"github.com/charmbracelet/lipgloss"
)
const (
fullPercent = 100
highWaterPercent = 80
formatColumn = 7
unboundedTag = "unbounded"
)
type capRow struct {
format string
applicable bool
unbounded bool
capacity int
maxPlaintext int
maxEncrypted int
note string
}
func renderHideMeter(payloadLen, envelope, capacity int, capErr error, barWidth int) string {
if capErr != nil {
return styleWarn.Render(reasonText(capErr))
}
if capacity >= math.MaxInt32 {
bar := gradientText(strings.Repeat(blockFull, barWidth), unboundedStops, false)
return lipgloss.JoinVertical(lipgloss.Left,
bar+" "+styleSuccess.Render(unboundedTag),
"",
kv("payload", fmt.Sprintf("%d B", payloadLen)),
kv("envelope", fmt.Sprintf("%d B", envelope)),
styleSuccess.Render("this carrier has effectively unbounded room; your payload always fits"),
)
}
if capacity <= 0 {
return styleWarn.Render("this cover is too small to hold a payload")
}
frac := float64(envelope) / float64(capacity)
over := envelope > capacity
pct := int(math.Round(math.Min(frac, 1) * fullPercent))
bar := spectralBar(barWidth, frac, capacityStops)
pctStyle := styleSuccess
if over {
pctStyle = styleError
} else if pct >= highWaterPercent {
pctStyle = styleWarn
}
lines := []string{
bar + " " + pctStyle.Render(fmt.Sprintf("%d%%", pct)),
"",
kv("payload", fmt.Sprintf("%d B", payloadLen)),
kv("envelope", fmt.Sprintf("%d B", envelope)),
kv("capacity", fmt.Sprintf("%d B", capacity)),
}
if over {
lines = append(lines, "", styleError.Render(
fmt.Sprintf("payload exceeds capacity by %d B: shorten it, enable compression, or pick a roomier carrier", envelope-capacity)))
} else {
lines = append(lines, "", styleSuccess.Render(
fmt.Sprintf("fits with %d B of headroom", capacity-envelope)))
}
return lipgloss.JoinVertical(lipgloss.Left, lines...)
}
func renderCapacityTable(rows []capRow, barWidth int) string {
maxCap := 0
for _, r := range rows {
if r.applicable && !r.unbounded && r.capacity > maxCap {
maxCap = r.capacity
}
}
out := make([]string, 0, len(rows))
for _, r := range rows {
name := styleValueBold.Width(formatColumn).Render(r.format)
switch {
case !r.applicable:
out = append(out, name+styleHint.Render(r.note))
case r.unbounded:
bar := gradientText(strings.Repeat(blockFull, barWidth), unboundedStops, false)
out = append(out, name+bar+" "+styleHint.Render(unboundedTag))
default:
frac := 0.0
if maxCap > 0 {
frac = float64(r.capacity) / float64(maxCap)
}
bar := spectralBar(barWidth, frac, capacityStops)
out = append(out, name+bar+" "+capacityFacts(r))
}
}
return lipgloss.JoinVertical(lipgloss.Left, out...)
}
func capacityFacts(r capRow) string {
plain := styleValue.Render(fmt.Sprintf("%d B", r.maxPlaintext)) + styleLabel.Render(" plain")
var enc string
if r.maxEncrypted <= 0 {
enc = styleWarn.Render("encrypted does not fit")
} else {
enc = styleValue.Render(fmt.Sprintf("%d B", r.maxEncrypted)) + styleLabel.Render(" encrypted")
}
return plain + styleHelpSep.Render(" · ") + enc
}
func reasonText(err error) string {
msg := err.Error()
if i := strings.Index(msg, ": "); i >= 0 {
return msg[i+2:]
}
return msg
}
func clampZero(n int) int {
if n < 0 {
return 0
}
return n
}