diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index fc285a7..4482b14 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -5,6 +5,9 @@ on: permissions: checks: write pull-requests: write + contents: read + issues: write jobs: ci: + name: Continuous Integration uses: PSInclusive/.github/.github/workflows/ModuleCI.yml@main diff --git a/Accessibility/Private/Get-ColorBlindPaletteData.ps1 b/Accessibility/Private/Get-ColorBlindPaletteData.ps1 new file mode 100644 index 0000000..444a9fd --- /dev/null +++ b/Accessibility/Private/Get-ColorBlindPaletteData.ps1 @@ -0,0 +1,105 @@ +# Okabe-Ito palette: peer-reviewed, safe across all common colorblindness types. +# Source: https://jfly.uni-koeln.de/color/ +$script:OKABE_ITO = @{ + Black = @{ R = 0; G = 0; B = 0 } + Orange = @{ R = 230; G = 159; B = 0 } + SkyBlue = @{ R = 86; G = 180; B = 233 } + BluishGreen = @{ R = 0; G = 158; B = 115 } + Yellow = @{ R = 240; G = 228; B = 66 } + Blue = @{ R = 0; G = 114; B = 178 } + Vermillion = @{ R = 213; G = 94; B = 0 } + ReddishPurple = @{ R = 204; G = 121; B = 167 } +} + +function Get-ColorBlindPaletteData { + <# + .SYNOPSIS + Returns the RGB color table for a named color blind profile. + + .DESCRIPTION + Returns a hashtable of named color entries (each with R, G, B keys) for the specified + color blindness profile. All profiles are derived from the Okabe-Ito palette, adjusted + per the specific perceptual needs of each condition. + + .PARAMETER ProfileType + The color blindness profile to retrieve. Valid values: Deuteranopia, Protanopia, + Tritanopia, Achromatopsia, AccessibleDefault. + + .EXAMPLE + Get-ColorBlindPaletteData -ProfileType Deuteranopia + #> + param ( + [Parameter(Mandatory = $true)] + [ValidateSet('Deuteranopia', 'Protanopia', 'Tritanopia', 'Achromatopsia', 'AccessibleDefault')] + [string]$ProfileType + ) + + switch ($ProfileType) { + 'Deuteranopia' { + # Red-green deficiency (most common). Maximize blue/orange/yellow contrast. + # Red and green are shifted to orange and blue respectively. + @{ + Error = $script:OKABE_ITO.Vermillion # Vermillion (not red) + Warning = $script:OKABE_ITO.Orange # Orange + Success = $script:OKABE_ITO.Blue # Blue (not green) + Info = $script:OKABE_ITO.SkyBlue # Sky Blue + Highlight = $script:OKABE_ITO.Yellow # Yellow + Accent = $script:OKABE_ITO.ReddishPurple # Reddish Purple + Text = $script:OKABE_ITO.Black # Black + Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray (not part of Okabe-Ito) + } + } + 'Protanopia' { + # Red-weak (cannot perceive red). Red-range hues shifted further toward orange/yellow. + @{ + Error = @{ R = 230; G = 159; B = 0 } # Orange (red is invisible) + Warning = @{ R = 240; G = 228; B = 66 } # Yellow + Success = @{ R = 0; G = 114; B = 178 } # Blue + Info = @{ R = 86; G = 180; B = 233 } # Sky Blue + Highlight = @{ R = 204; G = 121; B = 167 } # Reddish Purple + Accent = @{ R = 0; G = 158; B = 115 } # Bluish Green + Text = @{ R = 0; G = 0; B = 0 } # Black + Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray + } + } + 'Tritanopia' { + # Blue-yellow deficiency. Shift to red/green contrast; replace blue with high-contrast cyan. + @{ + Error = @{ R = 213; G = 94; B = 0 } # Vermillion + Warning = @{ R = 204; G = 121; B = 167 } # Reddish Purple (not yellow) + Success = @{ R = 0; G = 158; B = 115 } # Bluish Green + Info = @{ R = 0; G = 200; B = 200 } # Cyan (high contrast, not pure blue) + Highlight = @{ R = 255; G = 100; B = 100 } # Light Red (visible to tritanopes) + Accent = @{ R = 180; G = 60; B = 60 } # Dark Red + Text = @{ R = 0; G = 0; B = 0 } # Black + Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray + } + } + 'Achromatopsia' { + # Full colorblindness. High-contrast grayscale ladder designed for strong separation between steps. + @{ + Error = @{ R = 30; G = 30; B = 30 } # Near-black (on white bg) + Warning = @{ R = 80; G = 80; B = 80 } # Dark gray + Success = @{ R = 50; G = 50; B = 50 } # Dark gray variant + Info = @{ R = 110; G = 110; B = 110 } # Medium gray + Highlight = @{ R = 230; G = 230; B = 230 } # Near-white + Accent = @{ R = 0; G = 0; B = 0 } # Black + Text = @{ R = 0; G = 0; B = 0 } # Black + Muted = @{ R = 160; G = 160; B = 160 } # Light gray + } + } + 'AccessibleDefault' { + # Okabe-Ito as-is: a safe general baseline for users unsure of their type. + @{ + Error = @{ R = 213; G = 94; B = 0 } # Vermillion + Warning = @{ R = 230; G = 159; B = 0 } # Orange + Success = @{ R = 0; G = 158; B = 115 } # Bluish Green + Info = @{ R = 86; G = 180; B = 233 } # Sky Blue + Highlight = @{ R = 240; G = 228; B = 66 } # Yellow + Accent = @{ R = 0; G = 114; B = 178 } # Blue + Text = @{ R = 0; G = 0; B = 0 } # Black + Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray + } + } + } +} diff --git a/Accessibility/Private/Get-ContrastRatio.ps1 b/Accessibility/Private/Get-ContrastRatio.ps1 new file mode 100644 index 0000000..913b577 --- /dev/null +++ b/Accessibility/Private/Get-ContrastRatio.ps1 @@ -0,0 +1,34 @@ +function Get-ContrastRatio { + <# + .SYNOPSIS + Calculates the WCAG contrast ratio between two relative luminance values. + + .DESCRIPTION + Implements the W3C WCAG 2.1 contrast ratio formula. Returns a ratio between 1:1 + (no contrast) and 21:1 (maximum contrast, black on white). + + .PARAMETER Luminance1 + Relative luminance of the first color (0.0 to 1.0). + + .PARAMETER Luminance2 + Relative luminance of the second color (0.0 to 1.0). + + .EXAMPLE + Get-ContrastRatio -Luminance1 1.0 -Luminance2 0.0 + # Returns 21.0 (white on black) + + .NOTES + https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio + #> + param ( + [Parameter(Mandatory = $true)] + [double]$Luminance1, + [Parameter(Mandatory = $true)] + [double]$Luminance2 + ) + + $lighter = [Math]::Max($Luminance1, $Luminance2) + $darker = [Math]::Min($Luminance1, $Luminance2) + + return ($lighter + 0.05) / ($darker + 0.05) +} diff --git a/Accessibility/Private/Get-RelativeLuminance.ps1 b/Accessibility/Private/Get-RelativeLuminance.ps1 new file mode 100644 index 0000000..b6a6674 --- /dev/null +++ b/Accessibility/Private/Get-RelativeLuminance.ps1 @@ -0,0 +1,52 @@ +function Get-RelativeLuminance { + <# + .SYNOPSIS + Calculates the relative luminance of an RGB color per the W3C WCAG 2.1 formula. + + .DESCRIPTION + Implements the W3C relative luminance formula required for WCAG contrast ratio + calculations. Returns a value between 0 (black) and 1 (white). + + .PARAMETER R + Red channel value (0-255). + + .PARAMETER G + Green channel value (0-255). + + .PARAMETER B + Blue channel value (0-255). + + .EXAMPLE + Get-RelativeLuminance -R 0 -G 0 -B 0 + # Returns 0 (black) + + .EXAMPLE + Get-RelativeLuminance -R 255 -G 255 -B 255 + # Returns 1 (white) + + .NOTES + https://www.w3.org/TR/WCAG21/#dfn-relative-luminance + #> + param ( + [Parameter(Mandatory = $true)] + [ValidateRange(0, 255)] + [int]$R, + [Parameter(Mandatory = $true)] + [ValidateRange(0, 255)] + [int]$G, + [Parameter(Mandatory = $true)] + [ValidateRange(0, 255)] + [int]$B + ) + + $channels = @($R / 255.0, $G / 255.0, $B / 255.0) + $linearized = $channels | ForEach-Object { + if ($_ -le 0.04045) { + $_ / 12.92 + } else { + [Math]::Pow(($_ + 0.055) / 1.055, 2.4) + } + } + + return (0.2126 * $linearized[0]) + (0.7152 * $linearized[1]) + (0.0722 * $linearized[2]) +} diff --git a/Accessibility/Public/ConvertTo-PlainText.ps1 b/Accessibility/Public/ConvertTo-PlainText.ps1 new file mode 100644 index 0000000..d9530f1 --- /dev/null +++ b/Accessibility/Public/ConvertTo-PlainText.ps1 @@ -0,0 +1,51 @@ +function ConvertTo-PlainText { + <# + .SYNOPSIS + Strips common ANSI color/style escape codes and PSStyle formatting from a string. + + .DESCRIPTION + Removes common ANSI/VT100 SGR-style escape sequences (colors, bold, italic, etc.) + from text, returning clean plain text without those decorations. Useful for + preparing terminal output for screen readers, plain-text logging, or piping to + tools that do not handle such escape codes. + + Supports pipeline input. + + .PARAMETER InputText + The text string to strip of ANSI color/style escape codes. + + .EXAMPLE + ConvertTo-PlainText -InputText "$($PSStyle.Bold)Hello$($PSStyle.BoldOff) World" + + Returns 'Hello World' with no formatting. + + .EXAMPLE + ConvertTo-Bionic "The quick brown fox" | ConvertTo-PlainText + + Converts text to bionic format then strips common ANSI color/style escape sequences + for screen reader use. + + .EXAMPLE + Get-Content .\log.txt -Raw | ConvertTo-PlainText + + Strips common ANSI color/style escape codes from a log file that may contain + colored output. + + .NOTES + https://www.w3.org/WAI/WCAG21/Techniques/general/G166 + #> + [CmdletBinding()] + param ( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + Position = 0 + )] + [string]$InputText + ) + + process { + # Matches ESC (0x1B) followed by [ and any sequence of parameter/intermediate bytes and a final byte + $InputText -replace '\x1B\[[0-9;]*[mABCDEFGHJKSTfhilmnprsu]', '' + } +} diff --git a/Accessibility/Public/Disable-ScreenReaderMode.ps1 b/Accessibility/Public/Disable-ScreenReaderMode.ps1 new file mode 100644 index 0000000..ed76242 --- /dev/null +++ b/Accessibility/Public/Disable-ScreenReaderMode.ps1 @@ -0,0 +1,40 @@ +function Disable-ScreenReaderMode { + <# + .SYNOPSIS + Restores normal ANSI-formatted terminal output after screen reader mode. + + .DESCRIPTION + Clears the screen reader mode flag set by Enable-ScreenReaderMode, allowing + Accessibility module commands to resume producing colored and styled terminal + output using ANSI escape sequences. + + .EXAMPLE + Disable-ScreenReaderMode + + Deactivates screen reader mode and restores ANSI-formatted output. + + .EXAMPLE + Enable-ScreenReaderMode + ConvertTo-Bionic "Example text" + Disable-ScreenReaderMode + + Temporarily uses screen reader mode for one operation, then restores normal output. + + .NOTES + https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast + #> + [CmdletBinding()] + param () + + $script:ScreenReaderMode = $false + + if ($script:PreviousOutputRendering) { + $PSStyle.OutputRendering = $script:PreviousOutputRendering + Write-Verbose "Screen reader mode disabled. Previous output rendering restored." + } + else { + # Fallback if no previous output rendering mode was saved. + $PSStyle.OutputRendering = [System.Management.Automation.OutputRendering]::Host + Write-Verbose "Screen reader mode disabled. Output rendering set to Host." + } +} diff --git a/Accessibility/Public/Enable-ScreenReaderMode.ps1 b/Accessibility/Public/Enable-ScreenReaderMode.ps1 new file mode 100644 index 0000000..aea755b --- /dev/null +++ b/Accessibility/Public/Enable-ScreenReaderMode.ps1 @@ -0,0 +1,40 @@ +function Enable-ScreenReaderMode { + <# + .SYNOPSIS + Optimizes terminal output for screen reader assistive technology. + + .DESCRIPTION + Sets a module-wide flag that signals all Accessibility module commands to produce + output that is compatible with screen readers: no ANSI color codes, no box-drawing + characters, simplified structure, and plain descriptive text only. + + Use Disable-ScreenReaderMode to return to normal output mode. + + .EXAMPLE + Enable-ScreenReaderMode + + Activates screen reader-friendly output mode for all Accessibility module commands. + + .EXAMPLE + Enable-ScreenReaderMode + ConvertTo-Bionic "The quick brown fox jumped over the lazy dog." + + Enables screen reader mode then runs a bionic conversion. Output will contain no + ANSI bold codes, just the plain text result. + + .NOTES + https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast + #> + [CmdletBinding()] + param () + + $script:ScreenReaderMode = $true + + if (-not (Get-Variable -Name PreviousOutputRendering -Scope Script -ErrorAction SilentlyContinue)) { + $script:PreviousOutputRendering = $PSStyle.OutputRendering + } + + $PSStyle.OutputRendering = [System.Management.Automation.OutputRendering]::PlainText + + Write-Verbose "Screen reader mode enabled. ANSI output suppressed." +} diff --git a/Accessibility/Public/Export-AccessibilityProfile.ps1 b/Accessibility/Public/Export-AccessibilityProfile.ps1 new file mode 100644 index 0000000..6917307 --- /dev/null +++ b/Accessibility/Public/Export-AccessibilityProfile.ps1 @@ -0,0 +1,41 @@ +function Export-AccessibilityProfile { + <# + .SYNOPSIS + Exports the current accessibility settings to a JSON file. + + .DESCRIPTION + Serializes all active accessibility settings (color blind profile, screen reader mode, + output rendering) to a JSON file at the specified path. The file can later be restored + using Import-AccessibilityProfile, making it easy to share configurations or persist + them outside of $PROFILE. + + .PARAMETER Path + The file path where the JSON settings file should be written. + + .EXAMPLE + Export-AccessibilityProfile -Path "$HOME\accessibility-settings.json" + + Exports the current settings to a JSON file in the user's home directory. + + .EXAMPLE + Set-ColorBlindProfile -ProfileType Deuteranopia + Export-AccessibilityProfile -Path ".\my-profile.json" + + Applies a color profile then exports it to a local file. + + .NOTES + https://www.w3.org/WAI/WCAG21/Understanding/ + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Path + ) + + $profile = Get-AccessibilityProfile + + if ($PSCmdlet.ShouldProcess($Path, "Export accessibility settings")) { + $profile | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Encoding UTF8 + Write-Verbose "Accessibility settings exported to: $Path" + } +} diff --git a/Accessibility/Public/Get-AccessibilityProfile.ps1 b/Accessibility/Public/Get-AccessibilityProfile.ps1 new file mode 100644 index 0000000..8bbca32 --- /dev/null +++ b/Accessibility/Public/Get-AccessibilityProfile.ps1 @@ -0,0 +1,34 @@ +function Get-AccessibilityProfile { + <# + .SYNOPSIS + Returns all currently active accessibility settings for the module. + + .DESCRIPTION + Returns a structured object summarizing every accessibility setting currently + active in the session: color blind profile, screen reader mode, and output + rendering mode. Useful for inspecting the current state before exporting or + sharing a configuration. + + .EXAMPLE + Get-AccessibilityProfile + + Returns a PSCustomObject with ColorBlindProfile, ScreenReaderMode, and + OutputRendering properties. + + .EXAMPLE + (Get-AccessibilityProfile).ScreenReaderMode + + Returns $true if screen reader mode is active, $false otherwise. + + .NOTES + https://www.w3.org/WAI/WCAG21/Understanding/ + #> + [CmdletBinding()] + param () + + [PSCustomObject]@{ + ColorBlindProfile = $script:ActiveColorProfile + ScreenReaderMode = [bool]$script:ScreenReaderMode + OutputRendering = $PSStyle.OutputRendering.ToString() + } +} diff --git a/Accessibility/Public/Get-ColorBlindPalette.ps1 b/Accessibility/Public/Get-ColorBlindPalette.ps1 new file mode 100644 index 0000000..74cce56 --- /dev/null +++ b/Accessibility/Public/Get-ColorBlindPalette.ps1 @@ -0,0 +1,53 @@ +function Get-ColorBlindPalette { + <# + .SYNOPSIS + Returns the color table for a named color blindness profile. + + .DESCRIPTION + Returns a PSCustomObject containing the RGB values for each semantic color role + (Error, Warning, Success, Info, Highlight, Accent, Text, Muted) in the specified + color blindness profile. Useful for scripts and tools that need to programmatically + select accessible colors without applying them to the terminal directly. + + .PARAMETER ProfileType + The color blindness profile whose palette to retrieve. + + .EXAMPLE + Get-ColorBlindPalette -ProfileType Deuteranopia + + Returns the full Deuteranopia color table as a structured object. + + .EXAMPLE + (Get-ColorBlindPalette -ProfileType AccessibleDefault).Warning + + Returns the R, G, B values for the Warning color in the AccessibleDefault palette. + + .EXAMPLE + 'Deuteranopia','Protanopia','Tritanopia' | ForEach-Object { Get-ColorBlindPalette -ProfileType $_ } + + Returns the color tables for multiple profiles. + + .NOTES + https://jfly.uni-koeln.de/color/ + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, Position = 0)] + [ValidateSet('Deuteranopia', 'Protanopia', 'Tritanopia', 'Achromatopsia', 'AccessibleDefault')] + [string]$ProfileType + ) + + $palette = Get-ColorBlindPaletteData -ProfileType $ProfileType + + [PSCustomObject]@{ + ProfileType = $ProfileType + Error = [PSCustomObject]$palette.Error + Warning = [PSCustomObject]$palette.Warning + Success = [PSCustomObject]$palette.Success + Info = [PSCustomObject]$palette.Info + Highlight = [PSCustomObject]$palette.Highlight + Accent = [PSCustomObject]$palette.Accent + Text = [PSCustomObject]$palette.Text + Muted = [PSCustomObject]$palette.Muted + } +} diff --git a/Accessibility/Public/Get-ColorBlindProfile.ps1 b/Accessibility/Public/Get-ColorBlindProfile.ps1 new file mode 100644 index 0000000..20b3750 --- /dev/null +++ b/Accessibility/Public/Get-ColorBlindProfile.ps1 @@ -0,0 +1,39 @@ +function Get-ColorBlindProfile { + <# + .SYNOPSIS + Displays the currently active color blind profile and its color table. + + .DESCRIPTION + Returns a structured object showing the active color blind profile name and the + RGB color values assigned to each semantic terminal role (Error, Warning, Success, + Info, Highlight, Accent, Text, Muted). Returns $null if no profile has been set + in the current session. + + .EXAMPLE + Get-ColorBlindProfile + + Returns the active color blind profile object, or $null if none is set. + + .EXAMPLE + (Get-ColorBlindProfile).ProfileType + + Returns just the name of the active profile (e.g. 'Deuteranopia'). + + .NOTES + https://jfly.uni-koeln.de/color/ + #> + [CmdletBinding()] + param () + + if (-not $script:ActiveColorProfile) { + Write-Verbose "No color blind profile is currently active." + return $null + } + + $palette = Get-ColorBlindPaletteData -ProfileType $script:ActiveColorProfile + + [PSCustomObject]@{ + ProfileType = $script:ActiveColorProfile + Colors = $palette + } +} diff --git a/Accessibility/Public/Import-AccessibilityProfile.ps1 b/Accessibility/Public/Import-AccessibilityProfile.ps1 new file mode 100644 index 0000000..510c4e6 --- /dev/null +++ b/Accessibility/Public/Import-AccessibilityProfile.ps1 @@ -0,0 +1,60 @@ +function Import-AccessibilityProfile { + <# + .SYNOPSIS + Loads and applies accessibility settings from a JSON file. + + .DESCRIPTION + Reads a JSON settings file previously created by Export-AccessibilityProfile and + applies the contained settings to the current session. Supported settings include + color blind profile and screen reader mode. Unknown properties in the file are + silently ignored for forward compatibility. + + .PARAMETER Path + The path to the JSON settings file to import. + + .EXAMPLE + Import-AccessibilityProfile -Path "$HOME\accessibility-settings.json" + + Loads and applies settings from the specified file. + + .EXAMPLE + Import-AccessibilityProfile -Path ".\team-accessibility.json" + + Applies a shared team accessibility configuration from a local file. + + .NOTES + https://www.w3.org/WAI/WCAG21/Understanding/ + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Path + ) + + if (-not (Test-Path -Path $Path -PathType Leaf)) { + Write-Error "Settings file not found or is not a file: $Path" + return + } + + try { + $jsonContent = Get-Content -Path $Path -Raw -ErrorAction Stop + $settings = $jsonContent | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-Error "Failed to read or parse accessibility settings file as valid JSON: $Path. $_" + return + } + + if ($PSCmdlet.ShouldProcess($Path, "Import and apply accessibility settings")) { + if ($settings.ColorBlindProfile) { + Set-ColorBlindProfile -ProfileType $settings.ColorBlindProfile + } + + if ($settings.ScreenReaderMode -eq $true) { + Enable-ScreenReaderMode + } elseif ($settings.ScreenReaderMode -eq $false) { + Disable-ScreenReaderMode + } + + Write-Verbose "Accessibility settings imported from: $Path" + } +} diff --git a/Accessibility/Public/Reset-ColorProfile.ps1 b/Accessibility/Public/Reset-ColorProfile.ps1 new file mode 100644 index 0000000..88c78e4 --- /dev/null +++ b/Accessibility/Public/Reset-ColorProfile.ps1 @@ -0,0 +1,38 @@ +function Reset-ColorProfile { + <# + .SYNOPSIS + Restores terminal formatting colors to PowerShell's built-in defaults. + + .DESCRIPTION + Clears any color blind profile applied by Set-ColorBlindProfile by resetting + $PSStyle.Formatting properties back to their default values. Also clears the + active profile name tracked by the module. + + This does not remove any Set-ColorBlindProfile entry from $PROFILE. To stop a + persisted profile from loading in future sessions, edit $PROFILE manually and + remove the Set-ColorBlindProfile line. + + .EXAMPLE + Reset-ColorProfile + + Resets all terminal formatting colors to PowerShell defaults. + + .NOTES + https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ansi_terminals + #> + [CmdletBinding(SupportsShouldProcess)] + param () + + if ($PSCmdlet.ShouldProcess("terminal color scheme", "Reset to PowerShell defaults")) { + $PSStyle.Formatting.Error = $PSStyle.Foreground.Red + $PSStyle.Formatting.Warning = $PSStyle.Foreground.Yellow + $PSStyle.Formatting.Verbose = $PSStyle.Foreground.Cyan + $PSStyle.Formatting.Debug = $PSStyle.Foreground.Yellow + $PSStyle.Formatting.TableHeader = "$($PSStyle.Bold)$($PSStyle.Foreground.White)" + $PSStyle.Formatting.CustomTableRow = '' + + $script:ActiveColorProfile = $null + + Write-Verbose "Terminal colors reset to PowerShell defaults." + } +} diff --git a/Accessibility/Public/Set-ColorBlindProfile.ps1 b/Accessibility/Public/Set-ColorBlindProfile.ps1 new file mode 100644 index 0000000..09a4865 --- /dev/null +++ b/Accessibility/Public/Set-ColorBlindProfile.ps1 @@ -0,0 +1,89 @@ +function Set-ColorBlindProfile { + <# + .SYNOPSIS + Applies a color blind-friendly color scheme to the current terminal session. + + .DESCRIPTION + Remaps PowerShell's $PSStyle formatting colors to an accessible palette based on the + specified color blindness type. All profiles use colors derived from the peer-reviewed + Okabe-Ito palette, which is safe across all common colorblindness conditions. + + Use -Persist to automatically apply the profile in every new PowerShell session by + appending the invocation to your $PROFILE file. Use Reset-ColorProfile to undo. + + Supported profiles: + Deuteranopia - Red-green deficiency (most common). Blue/orange/yellow contrast. + Protanopia - Red-weak. Red-range hues shifted to orange/yellow. + Tritanopia - Blue-yellow deficiency. Red/green contrast with high-contrast cyan. + Achromatopsia - Full colorblindness. High-contrast grayscale ladder (WCAG AAA). + AccessibleDefault - Okabe-Ito baseline. Good starting point if unsure of type. + + .PARAMETER ProfileType + The color blindness profile to apply. + + .PARAMETER Persist + If specified, appends the Set-ColorBlindProfile call to $PROFILE so the color scheme + is automatically restored in every new PowerShell session. + + .EXAMPLE + Set-ColorBlindProfile -ProfileType Deuteranopia + + Applies the Deuteranopia-optimized color scheme to the current session. + + .EXAMPLE + Set-ColorBlindProfile -ProfileType AccessibleDefault -Persist + + Applies the general accessible color scheme and saves it to $PROFILE so it applies + automatically in future sessions. + + .EXAMPLE + Set-ColorBlindProfile -ProfileType Achromatopsia -WhatIf + + Shows what colors would be changed without applying them. + + .NOTES + Okabe-Ito palette: https://jfly.uni-koeln.de/color/ + WCAG contrast guidelines: https://www.w3.org/TR/WCAG21/#contrast-minimum + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true, Position = 0)] + [ValidateSet('Deuteranopia', 'Protanopia', 'Tritanopia', 'Achromatopsia', 'AccessibleDefault')] + [string]$ProfileType, + + [Parameter(Mandatory = $false)] + [switch]$Persist + ) + + $palette = Get-ColorBlindPaletteData -ProfileType $ProfileType + + if ($PSCmdlet.ShouldProcess("terminal color scheme", "Apply $ProfileType profile")) { + $PSStyle.Formatting.Error = $PSStyle.Foreground.FromRgb($palette.Error.R, $palette.Error.G, $palette.Error.B) + $PSStyle.Formatting.Warning = $PSStyle.Foreground.FromRgb($palette.Warning.R, $palette.Warning.G, $palette.Warning.B) + $PSStyle.Formatting.Verbose = $PSStyle.Foreground.FromRgb($palette.Info.R, $palette.Info.G, $palette.Info.B) + $PSStyle.Formatting.Debug = $PSStyle.Foreground.FromRgb($palette.Muted.R, $palette.Muted.G, $palette.Muted.B) + $PSStyle.Formatting.TableHeader = $PSStyle.Foreground.FromRgb($palette.Accent.R, $palette.Accent.G, $palette.Accent.B) + $PSStyle.Formatting.CustomTableRow = $PSStyle.Foreground.FromRgb($palette.Text.R, $palette.Text.G, $palette.Text.B) + + $script:ActiveColorProfile = $ProfileType + + Write-Verbose "Applied $ProfileType color profile to current session." + + if ($Persist) { + $invocation = "Set-ColorBlindProfile -ProfileType $ProfileType" + $profileDir = Split-Path -Path $PROFILE -Parent + if (-not (Test-Path -LiteralPath $profileDir)) { + New-Item -Path $profileDir -ItemType Directory -Force | Out-Null + } + + $profileContent = if (Test-Path -LiteralPath $PROFILE) { Get-Content -LiteralPath $PROFILE } else { @() } + if ($profileContent -notcontains $invocation) { + Add-Content -LiteralPath $PROFILE -Value "`n$invocation" + Write-Verbose "Saved $ProfileType profile to `$PROFILE: $PROFILE" + } + else { + Write-Verbose "Profile `$PROFILE already contains invocation for $ProfileType; skipping persist." + } + } + } +} diff --git a/Accessibility/Public/Test-ColorContrast.ps1 b/Accessibility/Public/Test-ColorContrast.ps1 new file mode 100644 index 0000000..0c849dd --- /dev/null +++ b/Accessibility/Public/Test-ColorContrast.ps1 @@ -0,0 +1,63 @@ +function Test-ColorContrast { + <# + .SYNOPSIS + Calculates the WCAG contrast ratio between two RGB colors and checks accessibility compliance. + + .DESCRIPTION + Given foreground and background colors as RGB integer arrays, calculates the contrast + ratio per WCAG 2.1 and returns a result object showing the ratio and whether it meets + WCAG AA (4.5:1 normal text, 3:1 large text) and AAA (7:1 normal text, 4.5:1 large text) + compliance thresholds. + + .PARAMETER Foreground + The foreground (text) color as an array of three integers: R, G, B (each 0-255). + + .PARAMETER Background + The background color as an array of three integers: R, G, B (each 0-255). + + .EXAMPLE + Test-ColorContrast -Foreground 0,0,0 -Background 255,255,255 + + Tests black text on white background. Returns a 21:1 ratio, passing both AA and AAA. + + .EXAMPLE + Test-ColorContrast -Foreground 213,94,0 -Background 255,255,255 + + Tests Okabe-Ito Vermillion on white. Returns contrast ratio and compliance result. + + .EXAMPLE + Test-ColorContrast -Foreground 128,128,128 -Background 255,255,255 + + Tests mid-gray on white to illustrate a failing AA contrast ratio. + + .NOTES + https://www.w3.org/TR/WCAG21/#contrast-minimum + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateCount(3, 3)] + [ValidateRange(0, 255)] + [int[]]$Foreground, + + [Parameter(Mandatory = $true)] + [ValidateCount(3, 3)] + [ValidateRange(0, 255)] + [int[]]$Background + ) + + $fgLuminance = Get-RelativeLuminance -R $Foreground[0] -G $Foreground[1] -B $Foreground[2] + $bgLuminance = Get-RelativeLuminance -R $Background[0] -G $Background[1] -B $Background[2] + $ratio = Get-ContrastRatio -Luminance1 $fgLuminance -Luminance2 $bgLuminance + $ratioRounded = [Math]::Round($ratio, 2) + + [PSCustomObject]@{ + Foreground = "rgb($($Foreground[0]),$($Foreground[1]),$($Foreground[2]))" + Background = "rgb($($Background[0]),$($Background[1]),$($Background[2]))" + ContrastRatio = $ratioRounded + PassesAA = $ratio -ge 4.5 + PassesAA_Large = $ratio -ge 3.0 + PassesAAA = $ratio -ge 7.0 + PassesAAA_Large = $ratio -ge 4.5 + } +}