[{"data":1,"prerenderedAt":46},["ShallowReactive",2],{"$flxZ4i8Nq-el_tjcylIMf_wx4IVsko2UGg_9qKWpIvVU":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"tools":12,"draft":14,"html":15,"toc":16,"minutes":45},"qr-code-encoder-from-scratch","How I Built a QR Code Encoder From the Standard, With No Library","Bits, Reed–Solomon error correction, interleaving, zigzag placement and masks, step by step, with the TypeScript behind ToolOnTap's QR code generator.","2026-10-18",[9,10,11],"algorithms","qr codes","typescript",[13],"qr-code-generator",false,"\u003Cp>When I added a \u003Ca href=\"\u002Fqr-code-generator\">QR code generator\u003C\u002Fa> to ToolOnTap, the easy route was a library, and there are good ones. I wrote the encoder myself anyway, for two reasons. I wanted to understand what’s inside those black and white squares, and I wanted every line of the page’s code to be something I could explain. The whole encoder is about 400 lines of TypeScript and has no dependencies.\u003C\u002Fp>\n\u003Cp>This article walks through it in the order the code runs. Everything here follows ISO\u002FIEC 18004, the QR code standard.\u003C\u002Fp>\n\u003Ch2 id=\"what-a-qr-code-is-made-of\">What a QR code is made of\u003C\u002Fh2>\n\u003Cp>A QR code is a square grid of modules (the small squares). There are 40 sizes, called versions: version 1 is 21 × 21 modules, and each version adds 4 per side, up to 177 × 177 for version 40.\u003C\u002Fp>\n\u003Cp>Some of the grid is fixed, the same in every code of that version:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Three finder patterns\u003C\u002Fstrong>, the big squares in the corners, which let a camera find the code and work out its rotation.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Two timing patterns\u003C\u002Fstrong>, alternating dark and light lines between the finders, which give the grid’s spacing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Alignment patterns\u003C\u002Fstrong>, smaller squares that help with bent or tilted codes, from version 2 up.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Format information\u003C\u002Fstrong>, 15 bits next to the finders that say which error correction level and mask were used. From version 7, there’s also \u003Cstrong>version information\u003C\u002Fstrong>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Everything else carries data. The job of an encoder is to turn your text into bits, protect them against damage, and place them in those free modules.\u003C\u002Fp>\n\u003Ch2 id=\"step-1-turn-the-text-into-bits\">Step 1: turn the text into bits\u003C\u002Fh2>\n\u003Cp>QR codes have several modes. Each packs a different kind of character more tightly:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Mode\u003C\u002Fth>\n\u003Cth>Characters\u003C\u002Fth>\n\u003Cth>Bits\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Numeric\u003C\u002Ftd>\n\u003Ctd>0–9\u003C\u002Ftd>\n\u003Ctd>10 bits per 3 digits\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Alphanumeric\u003C\u002Ftd>\n\u003Ctd>0–9, A–Z, space and \u003Ccode>$%*+-.\u002F:\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>11 bits per 2 characters\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Byte\u003C\u002Ftd>\n\u003Ctd>anything\u003C\u002Ftd>\n\u003Ctd>8 bits per byte\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Cp>So a phone number in numeric mode takes about 3.3 bits per digit instead of 8. My encoder picks the most compact mode that can hold the whole text. A link like \u003Ccode>https:\u002F\u002Ftoolontap.com\u003C\u002Fcode> has lowercase letters, so it goes in byte mode. Arabic text and emoji go in byte mode too, as UTF-8.\u003C\u002Fp>\n\u003Cp>Here’s numeric mode, three digits at a time:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (mode \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">===\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> 'numeric'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> text.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">    const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> chunk\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> text.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">slice\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(i, i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    b.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">Number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(chunk), chunk.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> *\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { bits: b.bits, count: text.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>chunk.length * 3 + 1\u003C\u002Fcode> gives 10 bits for three digits, 7 for two and 4 for one, which is exactly what the standard asks for.\u003C\u002Fp>\n\u003Cp>The data starts with a 4-bit mode indicator and the number of characters. The width of that count depends on the version: 8 bits for bytes in versions 1 to 9, 16 bits above that.\u003C\u002Fp>\n\u003Ch2 id=\"step-2-pick-the-smallest-version\">Step 2: pick the smallest version\u003C\u002Fh2>\n\u003Cp>Each version and error correction level has a fixed capacity. Version 1 at level M holds 16 data codewords (bytes). \u003Ccode>https:\u002F\u002Ftoolontap.com\u003C\u002Fcode> is 21 bytes, plus 12 bits of header, which doesn’t fit. Version 2 at level M holds 28, so the encoder uses version 2. The generator shows this under the code: “Version 2 · 25 × 25 · 80% full”.\u003C\u002Fp>\n\u003Cp>After the data comes a terminator of up to four zero bits. Then the bits are padded to a whole byte, and the rest of the capacity is filled with two bytes that alternate: \u003Ccode>0xEC\u003C\u002Fcode> and \u003Ccode>0x11\u003C\u002Fcode>. Those values are chosen in the standard so the padding doesn’t produce long runs of the same color.\u003C\u002Fp>\n\u003Ch2 id=\"step-3-reed-solomon-error-correction\">Step 3: Reed–Solomon error correction\u003C\u002Fh2>\n\u003Cp>This is the part that lets you scan a code with a coffee stain on it. Reed–Solomon codes add extra bytes computed from the data. A reader that gets some bytes wrong can find and fix them with the extra ones. There are four levels: L, M, Q and H recover from about 7%, 15%, 25% and 30% damage.\u003C\u002Fp>\n\u003Cp>The maths happens in GF(256), a field of 256 numbers where addition is XOR and multiplication wraps around a fixed polynomial. QR codes use x⁸ + x⁴ + x³ + x² + 1, which is \u003Ccode>0x11D\u003C\u002Fcode> in hex:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">function\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> gfMultiply\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">x\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">y\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> z \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 7\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">--\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    z \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (z \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">^\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> ((z \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">>>>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 7\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0x11D\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    z \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">^=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> ((y \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">>>>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> i) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x26;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> x\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> z\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>It’s ordinary long multiplication in binary: each step shifts left, and if a bit falls off the top, XOR with the polynomial folds it back into 8 bits.\u003C\u002Fp>\n\u003Cp>The error correction bytes are the remainder of dividing the data, as a polynomial, by a generator polynomial. It’s the same idea as the remainder in long division, which is why it can be computed one byte at a time.\u003C\u002Fp>\n\u003Cp>The standard includes a worked example: “01234567” at version 1, level M. I copied its data bytes and its 10 expected error correction bytes into a unit test. When that test passed, I knew the field arithmetic and the division were right before I’d drawn a single module.\u003C\u002Fp>\n\u003Ch2 id=\"step-4-split-into-blocks-and-interleave\">Step 4: split into blocks and interleave\u003C\u002Fh2>\n\u003Cp>Bigger codes don’t protect all their data as one long message. They split it into blocks, add error correction to each block, then interleave them: first byte of block 1, first byte of block 2, and so on.\u003C\u002Fp>\n\u003Cp>The reason is physical damage. A scratch across a code destroys bytes that sit next to each other. After interleaving, those neighbours belong to different blocks, so each block loses only a little and can repair itself. Some versions mix short and long blocks that differ by one byte, which is the fiddliest part of the code to get right.\u003C\u002Fp>\n\u003Ch2 id=\"step-5-place-the-bits-in-a-zigzag\">Step 5: place the bits in a zigzag\u003C\u002Fh2>\n\u003Cp>The bits go in two-module-wide columns, starting at the bottom right and moving up, then down the next pair of columns, then up again, skipping anything fixed:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> m.size \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">===\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 6\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 5\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#57606a;--shiki-dark:#8b949e\"> \u002F\u002F skip the vertical timing pattern\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> vert \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; vert \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> m.size; vert\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> j \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; j \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; j\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">      const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> x\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> j\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">      const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> upward\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> ((right \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x26;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">===\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">      const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> y\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> upward \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">?\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> m.size \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> -\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> vert \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> vert\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">      if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">m.fixed[y]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">[x] \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> data.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> *\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 8\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        m.modules[y]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">[x] \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> bit\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(data[i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">>>>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">7\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> -\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x26;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 7\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        i\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">      }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The \u003Ccode>right === 6\u003C\u002Fcode> line is a detail that’s easy to miss. Column 6 holds the vertical timing pattern, so the zigzag jumps over it.\u003C\u002Fp>\n\u003Ch2 id=\"step-6-choose-a-mask\">Step 6: choose a mask\u003C\u002Fh2>\n\u003Cp>Raw data can create patterns that confuse a camera: big solid areas, or shapes that look like a finder pattern. So the standard defines 8 masks, simple formulas such as “flip every module where (x + y) is even”. A mask is applied with XOR to the data modules only.\u003C\u002Fp>\n\u003Cp>The encoder tries all 8 and scores each result with the standard’s four penalty rules:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Runs of 5 or more same-colored modules in a row or column.\u003C\u002Fli>\n\u003Cli>2 × 2 blocks of one color.\u003C\u002Fli>\n\u003Cli>Patterns that look like a finder (dark-light-dark-dark-dark-light-dark with light space around it).\u003C\u002Fli>\n\u003Cli>An unbalanced share of dark modules, far from 50%.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>The mask with the lowest score wins. Its number goes in the format information, together with the error correction level, protected by its own small error-correcting code and XORed with \u003Ccode>0x5412\u003C\u002Fcode> so it’s never all zeros.\u003C\u002Fp>\n\u003Ch2 id=\"how-i-know-it-works\">How I know it works\u003C\u002Fh2>\n\u003Cp>Writing an encoder is one thing; knowing that phones can read it is another. I didn’t want to rely on my eyes or one phone, so the tests decode every code with \u003Cstrong>jsQR\u003C\u002Fstrong>, an independent open-source decoder that is only used by the tests and never shipped to the site. The tests cover:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>every error correction level, with links, Arabic text, emoji and a Wi‑Fi string full of characters that need escaping;\u003C\u002Fli>\n\u003Cli>all 8 masks, forced one by one;\u003C\u002Fli>\n\u003Cli>lengths from 10 to 2,200 characters, which reach the large versions where the block layouts get complicated;\u003C\u002Fli>\n\u003Cli>the capacity tables against the numbers printed in the standard.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>I also ran the downloaded PNG and SVG files from the real page through the same decoder in a browser.\u003C\u002Fp>\n\u003Ch2 id=\"what-i-left-out\">What I left out\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Mixed modes.\u003C\u002Fstrong> A text can switch modes partway, for example a long number inside a link, to save a few bits. My encoder uses one mode for the whole text, which is simpler and only costs a little space on long mixed content.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Kanji mode\u003C\u002Fstrong>, which packs Japanese characters more tightly, isn’t needed for the site’s users.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Logos in the middle.\u003C\u002Fstrong> They work because of error correction: you deliberately destroy part of the code and let H level repair it. It’s a trade-off I’d rather not make for people printing a menu or a Wi‑Fi sign.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The result is a \u003Ca href=\"\u002Fqr-code-generator\">QR code generator\u003C\u002Fa> that makes static codes for links, Wi‑Fi, WhatsApp, email and phone numbers, in your browser, with no library and no server.\u003C\u002Fp>\n",[17,21,24,27,30,33,36,39,42],{"id":18,"text":19,"level":20},"what-a-qr-code-is-made-of","What a QR code is made of",2,{"id":22,"text":23,"level":20},"step-1-turn-the-text-into-bits","Step 1: turn the text into bits",{"id":25,"text":26,"level":20},"step-2-pick-the-smallest-version","Step 2: pick the smallest version",{"id":28,"text":29,"level":20},"step-3-reed-solomon-error-correction","Step 3: Reed–Solomon error correction",{"id":31,"text":32,"level":20},"step-4-split-into-blocks-and-interleave","Step 4: split into blocks and interleave",{"id":34,"text":35,"level":20},"step-5-place-the-bits-in-a-zigzag","Step 5: place the bits in a zigzag",{"id":37,"text":38,"level":20},"step-6-choose-a-mask","Step 6: choose a mask",{"id":40,"text":41,"level":20},"how-i-know-it-works","How I know it works",{"id":43,"text":44,"level":20},"what-i-left-out","What I left out",6,1792325800713]