Skip to content

Instantly share code, notes, and snippets.

@alekrutkowski
Created August 24, 2026 11:42
Show Gist options
  • Select an option

  • Save alekrutkowski/d5d9b732d2a3b981f18ce89d76ccace4 to your computer and use it in GitHub Desktop.

Select an option

Save alekrutkowski/d5d9b732d2a3b981f18ce89d76ccace4 to your computer and use it in GitHub Desktop.
Windows clipboard contents ➜ QR code (SVG)
# 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"
@alekrutkowski

Copy link
Copy Markdown
Author

Download it and rename the extension to ico.
clipboard-qr ico

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment