Ocideck/lib/utils/color_contrast.dart
Brenno de Winter 1fc4d25dcf
Some checks are pending
CI / Format · Analyze · Test (push) Waiting to run
Add slide quality checks for accessibility with export warnings.
Help authors spot missing alt text, low contrast, and dense slides in the editor, with an optional confirmation step before export when issues remain.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 08:57:18 +02:00

45 lines
1.6 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
/// Parses a hex colour string (`#RRGGBB` or `RRGGBB`). Returns `null` when
/// invalid so callers can skip the pair instead of throwing.
Color? parseHexColor(String? value) {
if (value == null || value.trim().isEmpty) return null;
var hex = value.trim();
if (!hex.startsWith('#')) hex = '#$hex';
if (!RegExp(r'^#[0-9A-Fa-f]{6}$').hasMatch(hex)) return null;
return Color(int.parse(hex.substring(1), radix: 16) + 0xFF000000);
}
/// WCAG 2.1 relative luminance contrast ratio between two sRGB colours.
double contrastRatio(Color foreground, Color background) {
final l1 = foreground.computeLuminance();
final l2 = background.computeLuminance();
final lighter = math.max(l1, l2);
final darker = math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
/// Returns the contrast ratio for a hex pair, or `null` when either colour is
/// invalid.
double? hexContrastRatio(String foreground, String background) {
final fg = parseHexColor(foreground);
final bg = parseHexColor(background);
if (fg == null || bg == null) return null;
return contrastRatio(fg, bg);
}
/// WCAG 2.1 level AA thresholds.
const double kWcagAaNormalText = 4.5;
const double kWcagAaLargeText = 3.0;
/// Body text below this ratio is treated as a hard quality error.
const double kWcagCriticalBodyText = 3.0;
bool meetsWcagAa(String foreground, String background, {bool largeText = false}) {
final ratio = hexContrastRatio(foreground, background);
if (ratio == null) return true;
final threshold = largeText ? kWcagAaLargeText : kWcagAaNormalText;
return ratio >= threshold;
}