Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 3151231032497ed786b88a0a
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
functions/lib/qr.ts
// A small QR code encoder (ISO/IEC 18004: byte mode, error correction level M, versions 1 to 10),
// shared by the web app and the API so a link's QR code is identical everywhere.
// Short links are at most ~70 characters; version 10 holds 213.
/** modules[row][col], true is dark. */
export type QrMatrix = boolean[][];
// Level M, indexed by version.
const ECC_PER_BLOCK = [0, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26];
const BLOCKS = [0, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5];
function rawModules(ver: number): number {
let n = (16 * ver + 128) * ver + 64;
if (ver >= 2) {
const align = Math.floor(ver / 7) + 2;
n -= (25 * align - 10) * align - 55;
if (ver >= 7) n -= 36;
}
return n;
}
const dataCodewords = (ver: number) => Math.floor(rawModules(ver) / 8) - ECC_PER_BLOCK[ver] * BLOCKS[ver];
function gfMul(x: number, y: number): number {
let z = 0;
for (let i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >>> 7) * 0x11d);
z ^= ((y >>> i) & 1) * x;
}
return z;
}
function rsDivisor(degree: number): number[] {
const out = new Array(degree).fill(0);
out[degree - 1] = 1;
let root = 1;
for (let i = 0; i < degree; i++) {
for (let j = 0; j < degree; j++) {
out[j] = gfMul(out[j], root);
if (j + 1 < degree) out[j] ^= out[j + 1];
}
root = gfMul(root, 2);
}
return out;
}
function rsRemainder(data: number[], divisor: number[]): number[] {
const out = divisor.map(() => 0);
for (const b of data) {
const factor = b ^ (out.shift() as number);
out.push(0);
divisor.forEach((c, i) => { out[i] ^= gfMul(c, factor); });
}
return out;
}
/** Data codewords followed by error correction, split into blocks and interleaved. */
function codewords(bytes: Uint8Array, ver: number): number[] {
const capacity = dataCodewords(ver) * 8;
const bits: number[] = [];
const push = (value: number, length: number) => { for (let i = length - 1; i >= 0; i--) bits.push((value >>> i) & 1); };
push(0b0100, 4);
push(bytes.length, ver < 10 ? 8 : 16);
bytes.forEach((b) => push(b, 8));
push(0, Math.min(4, capacity - bits.length));
push(0, (8 - (bits.length % 8)) % 8);
for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11) push(pad, 8);
const data: number[] = [];
for (let i = 0; i < bits.length; i += 8) data.push(bits.slice(i, i + 8).reduce((a, b) => (a << 1) | b, 0));
const blocks = BLOCKS[ver];
const eccLen = ECC_PER_BLOCK[ver];
const raw = Math.floor(rawModules(ver) / 8);
const shortBlocks = blocks - (raw % blocks);
const shortLen = Math.floor(raw / blocks);
const divisor = rsDivisor(eccLen);
const out: number[][] = [];
for (let i = 0, k = 0; i < blocks; i++) {
const block = data.slice(k, k + shortLen - eccLen + (i < shortBlocks ? 0 : 1));
k += block.length;
const ecc = rsRemainder(block, divisor);
if (i < shortBlocks) block.push(0);
out.push(block.concat(ecc));
}
const result: number[] = [];
for (let i = 0; i < out[0].length; i++) {
out.forEach((block, j) => { if (i !== shortLen - eccLen || j >= shortBlocks) result.push(block[i]); });
}
return result;
}
const MASKS: ((r: number, c: number) => boolean)[] = [
(r, c) => (r + c) % 2 === 0,
(r) => r % 2 === 0,
(_, c) => c % 3 === 0,
(r, c) => (r + c) % 3 === 0,
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
(r, c) => ((r * c) % 2) + ((r * c) % 3) === 0,
(r, c) => (((r * c) % 2) + ((r * c) % 3)) % 2 === 0,
(r, c) => (((r + c) % 2) + ((r * c) % 3)) % 2 === 0,
];
/** Penalty points for a finished symbol (the four rules of ISO 18004 section 7.8.3). */
function penalty(m: QrMatrix): number {
const n = m.length;
let points = 0;
let dark = 0;
for (let a = 0; a < n; a++) {
let runRow = 0, runCol = 0, bitsRow = 0, bitsCol = 0;
for (let b = 0; b < n; b++) {
const row = m[a][b], col = m[b][a];
if (row) dark++;
runRow = b > 0 && row === m[a][b - 1] ? runRow + 1 : 1;
if (runRow === 5) points += 3; else if (runRow > 5) points++;
runCol = b > 0 && col === m[b - 1][a] ? runCol + 1 : 1;
if (runCol === 5) points += 3; else if (runCol > 5) points++;
bitsRow = ((bitsRow << 1) & 0x7ff) | (row ? 1 : 0);
bitsCol = ((bitsCol << 1) & 0x7ff) | (col ? 1 : 0);
if (b >= 10 && (bitsRow === 0x5d0 || bitsRow === 0x05d)) points += 40;
if (b >= 10 && (bitsCol === 0x5d0 || bitsCol === 0x05d)) points += 40;
if (a < n - 1 && b < n - 1) {
const sum = +m[a][b] + +m[a][b + 1] + +m[a + 1][b] + +m[a + 1][b + 1];
if (sum === 0 || sum === 4) points += 3;
}
}
}
return points + Math.abs(Math.ceil((dark * 100) / (n * n) / 5) - 10) * 10;
}
/** The QR code for text, as a grid of modules (no quiet zone). */
export function qrMatrix(text: string): QrMatrix {
const bytes = new TextEncoder().encode(text);
let ver = 1;
while (ver <= 10 && 4 + (ver < 10 ? 8 : 16) + bytes.length * 8 > dataCodewords(ver) * 8) ver++;
if (ver > 10) throw new Error("That is too long for a QR code.");
const size = ver * 4 + 17;
const m: QrMatrix = Array.from({ length: size }, () => new Array(size).fill(false));
const fixed: boolean[][] = Array.from({ length: size }, () => new Array(size).fill(false));
const put = (r: number, c: number, dark: boolean) => { m[r][c] = dark; fixed[r][c] = true; };
for (let i = 0; i < size; i++) { put(6, i, i % 2 === 0); put(i, 6, i % 2 === 0); }
for (const [cr, cc] of [[3, 3], [3, size - 4], [size - 4, 3]]) {
for (let dr = -4; dr <= 4; dr++) for (let dc = -4; dc <= 4; dc++) {
const r = cr + dr, c = cc + dc, d = Math.max(Math.abs(dr), Math.abs(dc));
if (r >= 0 && r < size && c >= 0 && c < size) put(r, c, d !== 2 && d !== 4);
}
}
if (ver >= 2) {
const count = Math.floor(ver / 7) + 2;
const step = Math.ceil((ver * 4 + 4) / (count * 2 - 2)) * 2;
const pos = [6];
for (let p = size - 7; pos.length < count; p -= step) pos.splice(1, 0, p);
pos.forEach((r, i) => pos.forEach((c, j) => {
if ((i === 0 && j === 0) || (i === 0 && j === count - 1) || (i === count - 1 && j === 0)) return;
for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) put(r + dr, c + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1);
}));
}
const drawFormat = (mask: number) => {
let rem = mask; // level M is 00
for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
const bits = ((mask << 10) | rem) ^ 0x5412;
const bit = (i: number) => ((bits >>> i) & 1) === 1;
for (let i = 0; i <= 5; i++) put(i, 8, bit(i));
put(7, 8, bit(6));
put(8, 8, bit(7));
put(8, 7, bit(8));
for (let i = 9; i < 15; i++) put(8, 14 - i, bit(i));
for (let i = 0; i < 8; i++) put(8, size - 1 - i, bit(i));
for (let i = 8; i < 15; i++) put(size - 15 + i, 8, bit(i));
put(size - 8, 8, true);
};
drawFormat(0);
if (ver >= 7) {
let rem = ver;
for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
const bits = (ver << 12) | rem;
for (let i = 0; i < 18; i++) {
const dark = ((bits >>> i) & 1) === 1;
const a = size - 11 + (i % 3), b = Math.floor(i / 3);
put(b, a, dark);
put(a, b, dark);
}
}
const data = codewords(bytes, ver);
let i = 0;
for (let right = size - 1; right >= 1; right -= 2) {
if (right === 6) right = 5;
for (let vert = 0; vert < size; vert++) {
for (let j = 0; j < 2; j++) {
const c = right - j;
const r = ((right + 1) & 2) === 0 ? size - 1 - vert : vert;
if (!fixed[r][c] && i < data.length * 8) {
m[r][c] = ((data[i >>> 3] >>> (7 - (i & 7))) & 1) === 1;
i++;
}
}
}
}
const applyMask = (mask: number) => {
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (!fixed[r][c] && MASKS[mask](r, c)) m[r][c] = !m[r][c];
};
let best = 0, bestScore = Infinity;
for (let mask = 0; mask < 8; mask++) {
drawFormat(mask);
applyMask(mask);
const score = penalty(m);
if (score < bestScore) { best = mask; bestScore = score; }
applyMask(mask);
}
drawFormat(best);
applyMask(best);
return m;
}
export const QR_COLORS = [
{ name: "Black", hex: "#111111" },
{ name: "Cobalt", hex: "#2e5ec4" },
{ name: "Forest", hex: "#2f5a3c" },
{ name: "Brick", hex: "#a8442c" },
{ name: "Plum", hex: "#5b3a6e" },
];
export const isHexColor = (s: string) => /^#[0-9a-f]{6}$/i.test(s);
/** Phones need enough contrast against the white background: at least 3 to 1. */
export function scansWell(hex: string): boolean {
const lin = (i: number) => {
const v = parseInt(hex.slice(i, i + 2), 16) / 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
};
const lum = 0.2126 * lin(1) + 0.7152 * lin(3) + 0.0722 * lin(5);
return 1.05 / (lum + 0.05) >= 3;
}
/** An SVG of the code with a 4-module quiet zone on white. */
export function qrSvg(m: QrMatrix, color = "#111111"): string {
const margin = 4;
const n = m.length + margin * 2;
let d = "";
m.forEach((row, r) => {
for (let c = 0; c < row.length;) {
if (!row[c]) { c++; continue; }
let end = c;
while (end < row.length && row[end]) end++;
d += `M${c + margin} ${r + margin}h${end - c}v1h-${end - c}z`;
c = end;
}
});
return `<svg xmlns="http://www.w3.org/2000/svg" width="${n * 10}" height="${n * 10}" viewBox="0 0 ${n} ${n}" shape-rendering="crispEdges"><rect width="${n}" height="${n}" fill="#ffffff"/><path fill="${color}" d="${d}"/></svg>`;
}
Change history
Quality pass: clicks per day, CSV export, links on other devices
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync