Telegram authentication requires you to open a chat on the phone. It's convenient to have a QR code for the case when you want to log in on the computer but have Telegram only on your phone and would be able to scan the QR instead of copy-pasting the link from the computer to the phone any other way. Originally we thought of generating QR on the client but found backend-generated QR a better alternative because we avoid adding one more JavaScript dependency to the frontend that way.
316 lines
6.2 KiB
Go
316 lines
6.2 KiB
Go
// go-qrcode
|
|
// Copyright 2014 Tom Harwood
|
|
|
|
package qrcode
|
|
|
|
import (
|
|
bitset "github.com/skip2/go-qrcode/bitset"
|
|
)
|
|
|
|
type regularSymbol struct {
|
|
version qrCodeVersion
|
|
mask int
|
|
|
|
data *bitset.Bitset
|
|
|
|
symbol *symbol
|
|
size int
|
|
}
|
|
|
|
// Abbreviated true/false.
|
|
const (
|
|
b0 = false
|
|
b1 = true
|
|
)
|
|
|
|
var (
|
|
alignmentPatternCenter = [][]int{
|
|
{}, // Version 0 doesn't exist.
|
|
{}, // Version 1 doesn't use alignment patterns.
|
|
{6, 18},
|
|
{6, 22},
|
|
{6, 26},
|
|
{6, 30},
|
|
{6, 34},
|
|
{6, 22, 38},
|
|
{6, 24, 42},
|
|
{6, 26, 46},
|
|
{6, 28, 50},
|
|
{6, 30, 54},
|
|
{6, 32, 58},
|
|
{6, 34, 62},
|
|
{6, 26, 46, 66},
|
|
{6, 26, 48, 70},
|
|
{6, 26, 50, 74},
|
|
{6, 30, 54, 78},
|
|
{6, 30, 56, 82},
|
|
{6, 30, 58, 86},
|
|
{6, 34, 62, 90},
|
|
{6, 28, 50, 72, 94},
|
|
{6, 26, 50, 74, 98},
|
|
{6, 30, 54, 78, 102},
|
|
{6, 28, 54, 80, 106},
|
|
{6, 32, 58, 84, 110},
|
|
{6, 30, 58, 86, 114},
|
|
{6, 34, 62, 90, 118},
|
|
{6, 26, 50, 74, 98, 122},
|
|
{6, 30, 54, 78, 102, 126},
|
|
{6, 26, 52, 78, 104, 130},
|
|
{6, 30, 56, 82, 108, 134},
|
|
{6, 34, 60, 86, 112, 138},
|
|
{6, 30, 58, 86, 114, 142},
|
|
{6, 34, 62, 90, 118, 146},
|
|
{6, 30, 54, 78, 102, 126, 150},
|
|
{6, 24, 50, 76, 102, 128, 154},
|
|
{6, 28, 54, 80, 106, 132, 158},
|
|
{6, 32, 58, 84, 110, 136, 162},
|
|
{6, 26, 54, 82, 110, 138, 166},
|
|
{6, 30, 58, 86, 114, 142, 170},
|
|
}
|
|
|
|
finderPattern = [][]bool{
|
|
{b1, b1, b1, b1, b1, b1, b1},
|
|
{b1, b0, b0, b0, b0, b0, b1},
|
|
{b1, b0, b1, b1, b1, b0, b1},
|
|
{b1, b0, b1, b1, b1, b0, b1},
|
|
{b1, b0, b1, b1, b1, b0, b1},
|
|
{b1, b0, b0, b0, b0, b0, b1},
|
|
{b1, b1, b1, b1, b1, b1, b1},
|
|
}
|
|
|
|
finderPatternSize = 7
|
|
|
|
finderPatternHorizontalBorder = [][]bool{
|
|
{b0, b0, b0, b0, b0, b0, b0, b0},
|
|
}
|
|
|
|
finderPatternVerticalBorder = [][]bool{
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
{b0},
|
|
}
|
|
|
|
alignmentPattern = [][]bool{
|
|
{b1, b1, b1, b1, b1},
|
|
{b1, b0, b0, b0, b1},
|
|
{b1, b0, b1, b0, b1},
|
|
{b1, b0, b0, b0, b1},
|
|
{b1, b1, b1, b1, b1},
|
|
}
|
|
)
|
|
|
|
func buildRegularSymbol(version qrCodeVersion, mask int,
|
|
data *bitset.Bitset, includeQuietZone bool) (*symbol, error) {
|
|
|
|
quietZoneSize := 0
|
|
if includeQuietZone {
|
|
quietZoneSize = version.quietZoneSize()
|
|
}
|
|
|
|
m := ®ularSymbol{
|
|
version: version,
|
|
mask: mask,
|
|
data: data,
|
|
|
|
symbol: newSymbol(version.symbolSize(), quietZoneSize),
|
|
size: version.symbolSize(),
|
|
}
|
|
|
|
m.addFinderPatterns()
|
|
m.addAlignmentPatterns()
|
|
m.addTimingPatterns()
|
|
m.addFormatInfo()
|
|
m.addVersionInfo()
|
|
|
|
ok, err := m.addData()
|
|
if !ok {
|
|
return nil, err
|
|
}
|
|
|
|
return m.symbol, nil
|
|
}
|
|
|
|
func (m *regularSymbol) addFinderPatterns() {
|
|
fpSize := finderPatternSize
|
|
fp := finderPattern
|
|
fpHBorder := finderPatternHorizontalBorder
|
|
fpVBorder := finderPatternVerticalBorder
|
|
|
|
// Top left Finder Pattern.
|
|
m.symbol.set2dPattern(0, 0, fp)
|
|
m.symbol.set2dPattern(0, fpSize, fpHBorder)
|
|
m.symbol.set2dPattern(fpSize, 0, fpVBorder)
|
|
|
|
// Top right Finder Pattern.
|
|
m.symbol.set2dPattern(m.size-fpSize, 0, fp)
|
|
m.symbol.set2dPattern(m.size-fpSize-1, fpSize, fpHBorder)
|
|
m.symbol.set2dPattern(m.size-fpSize-1, 0, fpVBorder)
|
|
|
|
// Bottom left Finder Pattern.
|
|
m.symbol.set2dPattern(0, m.size-fpSize, fp)
|
|
m.symbol.set2dPattern(0, m.size-fpSize-1, fpHBorder)
|
|
m.symbol.set2dPattern(fpSize, m.size-fpSize-1, fpVBorder)
|
|
}
|
|
|
|
func (m *regularSymbol) addAlignmentPatterns() {
|
|
for _, x := range alignmentPatternCenter[m.version.version] {
|
|
for _, y := range alignmentPatternCenter[m.version.version] {
|
|
if !m.symbol.empty(x, y) {
|
|
continue
|
|
}
|
|
|
|
m.symbol.set2dPattern(x-2, y-2, alignmentPattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *regularSymbol) addTimingPatterns() {
|
|
value := true
|
|
|
|
for i := finderPatternSize + 1; i < m.size-finderPatternSize; i++ {
|
|
m.symbol.set(i, finderPatternSize-1, value)
|
|
m.symbol.set(finderPatternSize-1, i, value)
|
|
|
|
value = !value
|
|
}
|
|
}
|
|
|
|
func (m *regularSymbol) addFormatInfo() {
|
|
fpSize := finderPatternSize
|
|
l := formatInfoLengthBits - 1
|
|
|
|
f := m.version.formatInfo(m.mask)
|
|
|
|
// Bits 0-7, under the top right finder pattern.
|
|
for i := 0; i <= 7; i++ {
|
|
m.symbol.set(m.size-i-1, fpSize+1, f.At(l-i))
|
|
}
|
|
|
|
// Bits 0-5, right of the top left finder pattern.
|
|
for i := 0; i <= 5; i++ {
|
|
m.symbol.set(fpSize+1, i, f.At(l-i))
|
|
}
|
|
|
|
// Bits 6-8 on the corner of the top left finder pattern.
|
|
m.symbol.set(fpSize+1, fpSize, f.At(l-6))
|
|
m.symbol.set(fpSize+1, fpSize+1, f.At(l-7))
|
|
m.symbol.set(fpSize, fpSize+1, f.At(l-8))
|
|
|
|
// Bits 9-14 on the underside of the top left finder pattern.
|
|
for i := 9; i <= 14; i++ {
|
|
m.symbol.set(14-i, fpSize+1, f.At(l-i))
|
|
}
|
|
|
|
// Bits 8-14 on the right side of the bottom left finder pattern.
|
|
for i := 8; i <= 14; i++ {
|
|
m.symbol.set(fpSize+1, m.size-fpSize+i-8, f.At(l-i))
|
|
}
|
|
|
|
// Always dark symbol.
|
|
m.symbol.set(fpSize+1, m.size-fpSize-1, true)
|
|
}
|
|
|
|
func (m *regularSymbol) addVersionInfo() {
|
|
fpSize := finderPatternSize
|
|
|
|
v := m.version.versionInfo()
|
|
l := versionInfoLengthBits - 1
|
|
|
|
if v == nil {
|
|
return
|
|
}
|
|
|
|
for i := 0; i < v.Len(); i++ {
|
|
// Above the bottom left finder pattern.
|
|
m.symbol.set(i/3, m.size-fpSize-4+i%3, v.At(l-i))
|
|
|
|
// Left of the top right finder pattern.
|
|
m.symbol.set(m.size-fpSize-4+i%3, i/3, v.At(l-i))
|
|
}
|
|
}
|
|
|
|
type direction uint8
|
|
|
|
const (
|
|
up direction = iota
|
|
down
|
|
)
|
|
|
|
func (m *regularSymbol) addData() (bool, error) {
|
|
xOffset := 1
|
|
dir := up
|
|
|
|
x := m.size - 2
|
|
y := m.size - 1
|
|
|
|
for i := 0; i < m.data.Len(); i++ {
|
|
var mask bool
|
|
switch m.mask {
|
|
case 0:
|
|
mask = (y+x+xOffset)%2 == 0
|
|
case 1:
|
|
mask = y%2 == 0
|
|
case 2:
|
|
mask = (x+xOffset)%3 == 0
|
|
case 3:
|
|
mask = (y+x+xOffset)%3 == 0
|
|
case 4:
|
|
mask = (y/2+(x+xOffset)/3)%2 == 0
|
|
case 5:
|
|
mask = (y*(x+xOffset))%2+(y*(x+xOffset))%3 == 0
|
|
case 6:
|
|
mask = ((y*(x+xOffset))%2+((y*(x+xOffset))%3))%2 == 0
|
|
case 7:
|
|
mask = ((y+x+xOffset)%2+((y*(x+xOffset))%3))%2 == 0
|
|
}
|
|
|
|
// != is equivalent to XOR.
|
|
m.symbol.set(x+xOffset, y, mask != m.data.At(i))
|
|
|
|
if i == m.data.Len()-1 {
|
|
break
|
|
}
|
|
|
|
// Find next free bit in the symbol.
|
|
for {
|
|
if xOffset == 1 {
|
|
xOffset = 0
|
|
} else {
|
|
xOffset = 1
|
|
|
|
if dir == up {
|
|
if y > 0 {
|
|
y--
|
|
} else {
|
|
dir = down
|
|
x -= 2
|
|
}
|
|
} else {
|
|
if y < m.size-1 {
|
|
y++
|
|
} else {
|
|
dir = up
|
|
x -= 2
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip over the vertical timing pattern entirely.
|
|
if x == 5 {
|
|
x--
|
|
}
|
|
|
|
if m.symbol.empty(x+xOffset, y) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|