Last active
September 24, 2025 14:08
-
-
Save khanlou/c0452b26b31dd554d04bc3b62f76133c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum APCA { | |
static func gamma(for channel: CGFloat) -> Double { | |
pow(channel, 2.4) | |
} | |
static func luminance(for color: RGBColor) -> Double { | |
let r = gamma(for: color.red) | |
let g = gamma(for: color.green) | |
let b = gamma(for: color.blue) | |
var y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b | |
if (y < 0.022) { | |
y += pow(0.022 - y, 1.414) | |
} | |
return y | |
} | |
static func contrast(foreground: RGBColor, background: RGBColor) -> Int { | |
let c = if (luminance(for: background) > luminance(for: foreground)) { | |
pow(luminance(for: background), 0.56) - pow(luminance(for: foreground), 0.57) | |
} else { | |
pow(luminance(for: background), 0.65) - pow(luminance(for: foreground), 0.62) | |
} | |
return Int(abs(1.14 * c * 100) - 2.7) | |
} | |
} | |
enum WCAG2 { | |
static func gamma(for channel: CGFloat) -> CGFloat { | |
if channel <= 0.03928 { | |
channel / 12.92 | |
} else { | |
pow((channel + 0.055)/1.055, 2.4) | |
} | |
} | |
static func luminance(for color: RGBColor) -> CGFloat { | |
0.2126 * gamma(for: color.red) | |
+ 0.7152 * gamma(for: color.green) | |
+ 0.0722 * gamma(for: color.blue) | |
} | |
static func contrast(foreground: RGBColor, background: RGBColor) -> CGFloat { | |
let brighter = max(luminance(for: foreground), luminance(for: background)) | |
let darker = min(luminance(for: foreground), luminance(for: background)) | |
return (brighter + 0.05) / (darker + 0.05) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension APCA { | |
static let fontSizeToMinimumLcByFontWeight: [(fontSize: Int, weights: [Int])] = [ | |
(12, [999, 999, 999, 999, 999, 999, 999, 999, 999]), | |
(14, [999, 999, 999, 100, 100, 90, 75, 999, 999]), | |
(15, [999, 999, 999, 100, 90, 75, 70, 999, 999]), | |
(16, [999, 999, 999, 90, 75, 70, 60, 60, 0]), | |
(18, [999, 999, 100, 75, 70, 60, 55, 55, 55]), | |
(21, [999, 999, 90, 70, 60, 55, 50, 50, 50]), | |
(24, [999, 999, 75, 60, 55, 50, 45, 45, 45]), | |
(28, [999, 100, 70, 55, 50, 45, 43, 43, 43]), | |
(32, [999, 90, 65, 50, 45, 43, 40, 40, 40]), | |
(36, [999, 75, 60, 45, 43, 40, 38, 38, 38]), | |
(42, [100, 70, 55, 43, 40, 38, 35, 35, 35]), | |
(48, [90, 60, 50, 40, 38, 35, 33, 33, 33]), | |
(60, [75, 55, 45, 38, 35, 33, 30, 30, 30]), | |
(72, [60, 50, 40, 35, 33, 30, 30, 30, 30]), | |
(96, [50, 45, 35, 33, 30, 30, 30, 30, 30]), | |
] | |
private static let weights: [SwiftUI.Font.Weight] = [.ultraLight, .thin, .light, .regular, .medium, .semibold, .bold, .heavy, .black] | |
static func passes(foreground: RGBColor, background: RGBColor, fontSize: CGFloat, fontWeight: SwiftUI.Font.Weight) -> Bool { | |
guard let fontWeightIndex = weights.firstIndex(of: fontWeight) else { return false } | |
guard let row = self.fontSizeToMinimumLcByFontWeight.last(where: { $0.fontSize <= Int(fontSize) }) else { return false } | |
let minimumLc = row.weights[fontWeightIndex] | |
let score = self.contrast(foreground: foreground, background: background) | |
return score > minimumLc | |
} | |
static func minimumFontWeight(foreground: RGBColor, background: RGBColor, fontSize: CGFloat) -> SwiftUI.Font.Weight { | |
let score = self.contrast(foreground: foreground, background: background) | |
guard let row = self.fontSizeToMinimumLcByFontWeight.last(where: { $0.fontSize <= Int(fontSize) }) else { return .black } | |
guard let index = row.weights.firstIndex(where: { $0 < score}) else { return .black } | |
return weights[index] | |
} | |
static func minimumFontSize(foreground: RGBColor, background: RGBColor, fontWeight: SwiftUI.Font.Weight) -> CGFloat? { | |
let score = self.contrast(foreground: foreground, background: background) | |
guard let index = weights.firstIndex(of: fontWeight) else { return nil } | |
let column = fontSizeToMinimumLcByFontWeight.map({ (fontSize: $0.fontSize, score: $0.weights[index] )}) | |
return column.first(where: { $0.score < score }).map({ CGFloat($0.fontSize) }) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct RGBColor: Hashable { | |
var red: CGFloat | |
var green: CGFloat | |
var blue: CGFloat | |
var alpha: CGFloat = 1 | |
init (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) { | |
self.red = red | |
self.green = green | |
self.blue = blue | |
self.alpha = alpha | |
} | |
} | |
extension RGBColor { | |
init(hex: Int) { | |
self.init( | |
red: Double(((hex >> 16) & 0xFF)) / 255, | |
green: Double(((hex >> 8) & 0xFF)) / 255, | |
blue: Double(((hex >> 0) & 0xFF)) / 255 | |
) | |
} | |
var hexString: String { | |
let red = Int(abs(self.red * 255)) << 16 | |
let green = Int(abs(self.green * 255)) << 8 | |
let blue = Int(abs(self.blue * 255)) | |
return String(format: "#%06X", red | green | blue) | |
} | |
} | |
extension RGBColor { | |
static let red: Self = .init(red: 1, green: 0, blue: 0) | |
static let green: Self = .init(red: 0, green: 1, blue: 0) | |
static let blue: Self = .init(red: 0, green: 0, blue: 1) | |
static let black: Self = .init(red: 0, green: 0, blue: 0) | |
static let white: Self = .init(red: 1, green: 1, blue: 1) | |
static let yellow: Self = .init(red: 1, green: 1, blue: 0) | |
static let cyan: Self = .init(red: 0, green: 1, blue: 1) | |
static let magenta: Self = .init(red: 1, green: 0, blue: 1) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment