Created
August 24, 2026 11:42
-
-
Save alekrutkowski/d5d9b732d2a3b981f18ce89d76ccace4 to your computer and use it in GitHub Desktop.
Windows clipboard contents ➜ QR code (SVG)
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
| # clipboard-qr-v5.ps1 | |
| # | |
| # Self-contained clipboard-to-QR utility for PowerShell 7 on Windows. | |
| # No modules, package repositories, proxy access, or network connection needed. | |
| # | |
| # The QR encoder is implemented directly in this script and writes an SVG file. | |
| # It uses QR Code Model 2, byte mode with UTF-8 ECI, error-correction level L, | |
| # and mask pattern 0. | |
| # Made by GPT-5.6 Extra High | |
| $ErrorActionPreference = 'Stop' | |
| # QR error-correction tables for level L, indexed by QR version (1..40). | |
| # Index 0 is unused. | |
| [int[]] $script:EccCodewordsPerBlock = @( | |
| 255, | |
| 7,10,15,20,26,18,20,24,30,18, | |
| 20,24,26,30,22,24,28,30,28,28, | |
| 28,28,30,30,26,28,30,30,30,30, | |
| 30,30,30,30,30,30,30,30,30,30 | |
| ) | |
| [int[]] $script:NumErrorCorrectionBlocks = @( | |
| 255, | |
| 1,1,1,1,1,2,2,2,2,4, | |
| 4,4,4,4,6,6,6,6,7,8, | |
| 8,9,9,10,12,12,12,13,14,15, | |
| 16,17,18,19,19,20,21,22,24,25 | |
| ) | |
| function Get-RawDataModuleCount { | |
| param( | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 40)] | |
| [int] $Version | |
| ) | |
| [int] $result = (16 * $Version + 128) * $Version + 64 | |
| if ($Version -ge 2) { | |
| [int] $numAlign = [int][Math]::Floor($Version / 7) + 2 | |
| $result -= (25 * $numAlign - 10) * $numAlign - 55 | |
| if ($Version -ge 7) { | |
| $result -= 36 | |
| } | |
| } | |
| return $result | |
| } | |
| function Get-GfProduct { | |
| param( | |
| [Parameter(Mandatory)] | |
| [ValidateRange(0, 255)] | |
| [int] $X, | |
| [Parameter(Mandatory)] | |
| [ValidateRange(0, 255)] | |
| [int] $Y | |
| ) | |
| [int] $z = 0 | |
| for ([int] $i = 7; $i -ge 0; $i--) { | |
| $z = ($z -shl 1) -bxor ((($z -shr 7) -band 1) * 0x11D) | |
| if ((($Y -shr $i) -band 1) -ne 0) { | |
| $z = $z -bxor $X | |
| } | |
| } | |
| return ($z -band 0xFF) | |
| } | |
| function Get-ReedSolomonDivisor { | |
| param( | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 255)] | |
| [int] $Degree | |
| ) | |
| [int[]] $result = [int[]]::new($Degree) | |
| $result[$Degree - 1] = 1 | |
| [int] $root = 1 | |
| for ([int] $i = 0; $i -lt $Degree; $i++) { | |
| for ([int] $j = 0; $j -lt $Degree; $j++) { | |
| $result[$j] = Get-GfProduct $result[$j] $root | |
| if ($j + 1 -lt $Degree) { | |
| $result[$j] = $result[$j] -bxor $result[$j + 1] | |
| } | |
| } | |
| $root = Get-GfProduct $root 2 | |
| } | |
| return ,$result | |
| } | |
| function Get-ReedSolomonRemainder { | |
| param( | |
| [Parameter(Mandatory)] | |
| [byte[]] $Data, | |
| [Parameter(Mandatory)] | |
| [int[]] $Divisor | |
| ) | |
| [int[]] $result = [int[]]::new($Divisor.Length) | |
| foreach ($b in $Data) { | |
| [int] $factor = [int]$b -bxor $result[0] | |
| for ([int] $i = 0; $i -lt $result.Length - 1; $i++) { | |
| $result[$i] = $result[$i + 1] | |
| } | |
| $result[$result.Length - 1] = 0 | |
| for ([int] $i = 0; $i -lt $result.Length; $i++) { | |
| $result[$i] = $result[$i] -bxor (Get-GfProduct $Divisor[$i] $factor) | |
| } | |
| } | |
| [byte[]] $bytes = [byte[]]::new($result.Length) | |
| for ([int] $i = 0; $i -lt $result.Length; $i++) { | |
| $bytes[$i] = [byte]$result[$i] | |
| } | |
| return ,$bytes | |
| } | |
| function Add-Bits { | |
| param( | |
| [Parameter(Mandatory)] | |
| [AllowEmptyCollection()] | |
| [System.Collections.Generic.List[int]] $Bits, | |
| [Parameter(Mandatory)] | |
| [int] $Value, | |
| [Parameter(Mandatory)] | |
| [ValidateRange(0, 31)] | |
| [int] $Length | |
| ) | |
| for ([int] $i = $Length - 1; $i -ge 0; $i--) { | |
| $Bits.Add(($Value -shr $i) -band 1) | |
| } | |
| } | |
| function Add-ErrorCorrectionAndInterleave { | |
| param( | |
| [Parameter(Mandatory)] | |
| [byte[]] $DataBytes, | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 40)] | |
| [int] $Version | |
| ) | |
| [int] $eccLength = $script:EccCodewordsPerBlock[$Version] | |
| [int] $numBlocks = $script:NumErrorCorrectionBlocks[$Version] | |
| [int] $rawCodewords = [int][Math]::Floor((Get-RawDataModuleCount $Version) / 8) | |
| [int] $numShortBlocks = $numBlocks - ($rawCodewords % $numBlocks) | |
| [int] $shortBlockLength = [int][Math]::Floor($rawCodewords / $numBlocks) | |
| [int] $shortDataLength = $shortBlockLength - $eccLength | |
| [int[]] $divisor = Get-ReedSolomonDivisor $eccLength | |
| $dataBlocks = [System.Collections.Generic.List[object]]::new() | |
| $eccBlocks = [System.Collections.Generic.List[object]]::new() | |
| [int] $offset = 0 | |
| for ([int] $block = 0; $block -lt $numBlocks; $block++) { | |
| [int] $dataLength = $shortDataLength | |
| if ($block -ge $numShortBlocks) { | |
| $dataLength++ | |
| } | |
| [byte[]] $currentData = [byte[]]::new($dataLength) | |
| [Array]::Copy($DataBytes, $offset, $currentData, 0, $dataLength) | |
| $offset += $dataLength | |
| [byte[]] $currentEcc = Get-ReedSolomonRemainder $currentData $divisor | |
| $dataBlocks.Add($currentData) | |
| $eccBlocks.Add($currentEcc) | |
| } | |
| if ($offset -ne $DataBytes.Length) { | |
| throw 'Internal QR encoding error while splitting data blocks.' | |
| } | |
| $result = [System.Collections.Generic.List[byte]]::new() | |
| for ([int] $i = 0; $i -le $shortDataLength; $i++) { | |
| for ([int] $block = 0; $block -lt $numBlocks; $block++) { | |
| [byte[]] $currentData = [byte[]]$dataBlocks[$block] | |
| if ($i -lt $currentData.Length) { | |
| $result.Add($currentData[$i]) | |
| } | |
| } | |
| } | |
| for ([int] $i = 0; $i -lt $eccLength; $i++) { | |
| for ([int] $block = 0; $block -lt $numBlocks; $block++) { | |
| [byte[]] $currentEcc = [byte[]]$eccBlocks[$block] | |
| $result.Add($currentEcc[$i]) | |
| } | |
| } | |
| if ($result.Count -ne $rawCodewords) { | |
| throw 'Internal QR encoding error while interleaving codewords.' | |
| } | |
| return ,$result.ToArray() | |
| } | |
| function ConvertTo-QrCodewords { | |
| param( | |
| [Parameter(Mandatory)] | |
| [AllowEmptyString()] | |
| [string] $Text | |
| ) | |
| [byte[]] $textBytes = [Text.Encoding]::UTF8.GetBytes($Text) | |
| [int] $version = 0 | |
| [int] $capacityBytes = 0 | |
| [int] $countBits = 0 | |
| for ([int] $candidate = 1; $candidate -le 40; $candidate++) { | |
| $countBits = if ($candidate -le 9) { 8 } else { 16 } | |
| $capacityBytes = | |
| [int][Math]::Floor((Get-RawDataModuleCount $candidate) / 8) - | |
| $script:EccCodewordsPerBlock[$candidate] * | |
| $script:NumErrorCorrectionBlocks[$candidate] | |
| # ECI mode indicator + UTF-8 ECI assignment 26 + byte-mode indicator | |
| # + byte count + payload. | |
| [int] $neededBits = 4 + 8 + 4 + $countBits + 8 * $textBytes.Length | |
| if ($neededBits -le 8 * $capacityBytes) { | |
| $version = $candidate | |
| break | |
| } | |
| } | |
| if ($version -eq 0) { | |
| throw "Clipboard text is too long to fit in a QR Code at error-correction level L." | |
| } | |
| $bits = [System.Collections.Generic.List[int]]::new() | |
| # ECI designator, assignment 26 = UTF-8. | |
| Add-Bits $bits 0x7 4 | |
| Add-Bits $bits 26 8 | |
| # Byte mode. | |
| Add-Bits $bits 0x4 4 | |
| Add-Bits $bits $textBytes.Length $countBits | |
| foreach ($b in $textBytes) { | |
| Add-Bits $bits $b 8 | |
| } | |
| [int] $capacityBits = 8 * $capacityBytes | |
| [int] $terminatorLength = [Math]::Min(4, $capacityBits - $bits.Count) | |
| Add-Bits $bits 0 $terminatorLength | |
| while (($bits.Count % 8) -ne 0) { | |
| $bits.Add(0) | |
| } | |
| $data = [System.Collections.Generic.List[byte]]::new() | |
| for ([int] $i = 0; $i -lt $bits.Count; $i += 8) { | |
| [int] $value = 0 | |
| for ([int] $j = 0; $j -lt 8; $j++) { | |
| $value = ($value -shl 1) -bor $bits[$i + $j] | |
| } | |
| $data.Add([byte]$value) | |
| } | |
| [byte[]] $padBytes = @(0xEC, 0x11) | |
| [int] $padIndex = 0 | |
| while ($data.Count -lt $capacityBytes) { | |
| $data.Add($padBytes[$padIndex % 2]) | |
| $padIndex++ | |
| } | |
| [byte[]] $interleaved = Add-ErrorCorrectionAndInterleave $data.ToArray() $version | |
| return [pscustomobject]@{ | |
| Version = $version | |
| Codewords = $interleaved | |
| } | |
| } | |
| function New-BoolMatrix { | |
| param( | |
| [Parameter(Mandatory)] | |
| [int] $Size | |
| ) | |
| [bool[][]] $matrix = [bool[][]]::new($Size) | |
| for ([int] $y = 0; $y -lt $Size; $y++) { | |
| $matrix[$y] = [bool[]]::new($Size) | |
| } | |
| return ,$matrix | |
| } | |
| function Set-FunctionModule { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [bool[][]] $IsFunction, | |
| [Parameter(Mandatory)] | |
| [int] $X, | |
| [Parameter(Mandatory)] | |
| [int] $Y, | |
| [Parameter(Mandatory)] | |
| [bool] $Dark | |
| ) | |
| $Modules[$Y][$X] = $Dark | |
| $IsFunction[$Y][$X] = $true | |
| } | |
| function Draw-FinderPattern { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [bool[][]] $IsFunction, | |
| [Parameter(Mandatory)] | |
| [int] $CenterX, | |
| [Parameter(Mandatory)] | |
| [int] $CenterY | |
| ) | |
| [int] $size = $Modules.Length | |
| for ([int] $dy = -4; $dy -le 4; $dy++) { | |
| for ([int] $dx = -4; $dx -le 4; $dx++) { | |
| [int] $x = $CenterX + $dx | |
| [int] $y = $CenterY + $dy | |
| if ($x -ge 0 -and $x -lt $size -and $y -ge 0 -and $y -lt $size) { | |
| [int] $distance = [Math]::Max([Math]::Abs($dx), [Math]::Abs($dy)) | |
| [bool] $dark = ($distance -ne 2 -and $distance -ne 4) | |
| Set-FunctionModule $Modules $IsFunction $x $y $dark | |
| } | |
| } | |
| } | |
| } | |
| function Draw-AlignmentPattern { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [bool[][]] $IsFunction, | |
| [Parameter(Mandatory)] | |
| [int] $CenterX, | |
| [Parameter(Mandatory)] | |
| [int] $CenterY | |
| ) | |
| for ([int] $dy = -2; $dy -le 2; $dy++) { | |
| for ([int] $dx = -2; $dx -le 2; $dx++) { | |
| [int] $distance = [Math]::Max([Math]::Abs($dx), [Math]::Abs($dy)) | |
| [bool] $dark = ($distance -ne 1) | |
| Set-FunctionModule ` | |
| $Modules ` | |
| $IsFunction ` | |
| ($CenterX + $dx) ` | |
| ($CenterY + $dy) ` | |
| $dark | |
| } | |
| } | |
| } | |
| function Get-AlignmentPatternPositions { | |
| param( | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 40)] | |
| [int] $Version | |
| ) | |
| if ($Version -eq 1) { | |
| return ,([int[]]@()) | |
| } | |
| [int] $numAlign = [int][Math]::Floor($Version / 7) + 2 | |
| [int] $step = if ($Version -eq 32) { | |
| 26 | |
| } | |
| else { | |
| [int][Math]::Floor( | |
| ($Version * 4 + $numAlign * 2 + 1) / | |
| ($numAlign * 2 - 2) | |
| ) * 2 | |
| } | |
| [int[]] $result = [int[]]::new($numAlign) | |
| $result[0] = 6 | |
| for ([int] $i = 0; $i -lt $numAlign - 1; $i++) { | |
| $result[$i + 1] = | |
| $Version * 4 + 10 - | |
| ($numAlign - 2 - $i) * $step | |
| } | |
| return ,$result | |
| } | |
| function Get-Bit { | |
| param( | |
| [Parameter(Mandatory)] | |
| [int] $Value, | |
| [Parameter(Mandatory)] | |
| [int] $Index | |
| ) | |
| return ((($Value -shr $Index) -band 1) -ne 0) | |
| } | |
| function Draw-FormatBits { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [bool[][]] $IsFunction | |
| ) | |
| # Error-correction level L has format value 1. | |
| # This implementation always uses mask pattern 0. | |
| [int] $data = (1 -shl 3) -bor 0 | |
| [int] $remainder = $data | |
| for ([int] $i = 0; $i -lt 10; $i++) { | |
| $remainder = | |
| ($remainder -shl 1) -bxor | |
| ((($remainder -shr 9) -band 1) * 0x537) | |
| } | |
| [int] $bits = (($data -shl 10) -bor $remainder) -bxor 0x5412 | |
| [int] $size = $Modules.Length | |
| for ([int] $i = 0; $i -le 5; $i++) { | |
| Set-FunctionModule $Modules $IsFunction 8 $i (Get-Bit $bits $i) | |
| } | |
| Set-FunctionModule $Modules $IsFunction 8 7 (Get-Bit $bits 6) | |
| Set-FunctionModule $Modules $IsFunction 8 8 (Get-Bit $bits 7) | |
| Set-FunctionModule $Modules $IsFunction 7 8 (Get-Bit $bits 8) | |
| for ([int] $i = 9; $i -le 14; $i++) { | |
| Set-FunctionModule $Modules $IsFunction (14 - $i) 8 (Get-Bit $bits $i) | |
| } | |
| for ([int] $i = 0; $i -le 7; $i++) { | |
| Set-FunctionModule $Modules $IsFunction ($size - 1 - $i) 8 (Get-Bit $bits $i) | |
| } | |
| for ([int] $i = 8; $i -le 14; $i++) { | |
| Set-FunctionModule $Modules $IsFunction 8 ($size - 15 + $i) (Get-Bit $bits $i) | |
| } | |
| # Fixed dark module. | |
| Set-FunctionModule $Modules $IsFunction 8 ($size - 8) $true | |
| } | |
| function Draw-VersionBits { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [bool[][]] $IsFunction, | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 40)] | |
| [int] $Version | |
| ) | |
| if ($Version -lt 7) { | |
| return | |
| } | |
| [int] $remainder = $Version | |
| for ([int] $i = 0; $i -lt 12; $i++) { | |
| $remainder = | |
| ($remainder -shl 1) -bxor | |
| ((($remainder -shr 11) -band 1) * 0x1F25) | |
| } | |
| [int] $bits = ($Version -shl 12) -bor $remainder | |
| [int] $size = $Modules.Length | |
| for ([int] $i = 0; $i -lt 18; $i++) { | |
| [bool] $bit = Get-Bit $bits $i | |
| [int] $a = $size - 11 + ($i % 3) | |
| [int] $b = [int][Math]::Floor($i / 3) | |
| Set-FunctionModule $Modules $IsFunction $a $b $bit | |
| Set-FunctionModule $Modules $IsFunction $b $a $bit | |
| } | |
| } | |
| function New-QrMatrix { | |
| param( | |
| [Parameter(Mandatory)] | |
| [ValidateRange(1, 40)] | |
| [int] $Version, | |
| [Parameter(Mandatory)] | |
| [byte[]] $Codewords | |
| ) | |
| [int] $size = $Version * 4 + 17 | |
| [bool[][]] $modules = New-BoolMatrix $size | |
| [bool[][]] $isFunction = New-BoolMatrix $size | |
| # Timing patterns. | |
| for ([int] $i = 0; $i -lt $size; $i++) { | |
| [bool] $dark = (($i % 2) -eq 0) | |
| Set-FunctionModule $modules $isFunction 6 $i $dark | |
| Set-FunctionModule $modules $isFunction $i 6 $dark | |
| } | |
| # Finder patterns and separators. | |
| Draw-FinderPattern $modules $isFunction 3 3 | |
| Draw-FinderPattern $modules $isFunction ($size - 4) 3 | |
| Draw-FinderPattern $modules $isFunction 3 ($size - 4) | |
| # Alignment patterns. | |
| [int[]] $positions = Get-AlignmentPatternPositions $Version | |
| for ([int] $i = 0; $i -lt $positions.Length; $i++) { | |
| for ([int] $j = 0; $j -lt $positions.Length; $j++) { | |
| [bool] $overlapsFinder = | |
| ($i -eq 0 -and $j -eq 0) -or | |
| ($i -eq 0 -and $j -eq $positions.Length - 1) -or | |
| ($i -eq $positions.Length - 1 -and $j -eq 0) | |
| if (-not $overlapsFinder) { | |
| Draw-AlignmentPattern ` | |
| $modules ` | |
| $isFunction ` | |
| $positions[$i] ` | |
| $positions[$j] | |
| } | |
| } | |
| } | |
| Draw-FormatBits $modules $isFunction | |
| Draw-VersionBits $modules $isFunction $Version | |
| # Place codeword bits in the standard two-column zig-zag pattern. | |
| [int] $bitIndex = 0 | |
| [int] $right = $size - 1 | |
| while ($right -ge 1) { | |
| if ($right -eq 6) { | |
| $right-- | |
| } | |
| for ([int] $vertical = 0; $vertical -lt $size; $vertical++) { | |
| [bool] $upward = ((($right + 1) -band 2) -eq 0) | |
| [int] $y = if ($upward) { | |
| $size - 1 - $vertical | |
| } | |
| else { | |
| $vertical | |
| } | |
| for ([int] $j = 0; $j -lt 2; $j++) { | |
| [int] $x = $right - $j | |
| if (-not $isFunction[$y][$x] -and $bitIndex -lt $Codewords.Length * 8) { | |
| [int] $byteIndex = $bitIndex -shr 3 | |
| [int] $bitInByte = 7 - ($bitIndex -band 7) | |
| $modules[$y][$x] = | |
| ((([int]$Codewords[$byteIndex] -shr $bitInByte) -band 1) -ne 0) | |
| $bitIndex++ | |
| } | |
| } | |
| } | |
| $right -= 2 | |
| } | |
| # Apply mask pattern 0: (x + y) mod 2 == 0. | |
| for ([int] $y = 0; $y -lt $size; $y++) { | |
| for ([int] $x = 0; $x -lt $size; $x++) { | |
| if ( | |
| -not $isFunction[$y][$x] -and | |
| (($x + $y) % 2) -eq 0 | |
| ) { | |
| $modules[$y][$x] = -not $modules[$y][$x] | |
| } | |
| } | |
| } | |
| return ,$modules | |
| } | |
| function Export-QrSvg { | |
| param( | |
| [Parameter(Mandatory)] | |
| [bool[][]] $Modules, | |
| [Parameter(Mandatory)] | |
| [string] $Path, | |
| [ValidateRange(0, 20)] | |
| [int] $QuietZone = 4, | |
| [ValidateRange(64, 4096)] | |
| [int] $DisplaySize = 600 | |
| ) | |
| [int] $matrixSize = $Modules.Length | |
| [int] $viewSize = $matrixSize + 2 * $QuietZone | |
| $svg = [Text.StringBuilder]::new() | |
| [void]$svg.AppendLine('<?xml version="1.0" encoding="UTF-8"?>') | |
| [void]$svg.AppendLine( | |
| '<svg xmlns="http://www.w3.org/2000/svg" ' + | |
| 'width="' + $DisplaySize + '" height="' + $DisplaySize + '" ' + | |
| 'viewBox="0 0 ' + $viewSize + ' ' + $viewSize + '" ' + | |
| 'shape-rendering="crispEdges">' | |
| ) | |
| [void]$svg.AppendLine( | |
| '<rect width="' + $viewSize + '" height="' + $viewSize + '" fill="white"/>' | |
| ) | |
| [void]$svg.AppendLine('<g fill="black">') | |
| for ([int] $y = 0; $y -lt $matrixSize; $y++) { | |
| for ([int] $x = 0; $x -lt $matrixSize; $x++) { | |
| if ($Modules[$y][$x]) { | |
| [void]$svg.Append( | |
| '<rect x="' + ($x + $QuietZone) + | |
| '" y="' + ($y + $QuietZone) + | |
| '" width="1" height="1"/>' | |
| ) | |
| } | |
| } | |
| } | |
| [void]$svg.AppendLine() | |
| [void]$svg.AppendLine('</g>') | |
| [void]$svg.AppendLine('</svg>') | |
| [IO.File]::WriteAllText( | |
| $Path, | |
| $svg.ToString(), | |
| [Text.UTF8Encoding]::new($false) | |
| ) | |
| } | |
| $text = Get-Clipboard -Raw | |
| if ([string]::IsNullOrEmpty($text)) { | |
| throw 'The clipboard does not contain any text.' | |
| } | |
| $encoded = ConvertTo-QrCodewords $text | |
| [bool[][]] $matrix = New-QrMatrix $encoded.Version $encoded.Codewords | |
| $out = Join-Path $env:TEMP ( | |
| 'clipboard-qr-{0}.svg' -f [guid]::NewGuid().ToString('N') | |
| ) | |
| Export-QrSvg ` | |
| -Modules $matrix ` | |
| -Path $out ` | |
| -QuietZone 4 ` | |
| -DisplaySize 600 | |
| if (-not (Test-Path -LiteralPath $out -PathType Leaf)) { | |
| throw "QR generation failed: '$out' was not created." | |
| } | |
| Start-Process -FilePath $out -ErrorAction Stop | |
| Write-Host "QR code saved to: $out" | |
| Write-Host "QR version: $($encoded.Version), error correction: L" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Download it and rename the extension to ico.
