1
0
Fork 0
inkscape/share/extensions/render_barcode_qrcode.py
Daniel Baumann 02d935e272
Adding upstream version 1.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 23:40:13 +02:00

1318 lines
41 KiB
Python
Executable file

#!/usr/bin/env python3
# coding=utf-8
#
# Copyright (C) 2009 Kazuhiko Arase (http://www.d-project.com/)
# 2010 Bulia Byak <buliabyak@gmail.com>
# 2018 Kirill Okhotnikov <kirill.okhotnikov@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA.
#
"""
Provide the QR Code rendering.
"""
from __future__ import print_function
from itertools import product
from collections import defaultdict
import numpy as np
import inkex
from inkex import Group, Rectangle, Use, PathElement
from inkex.paths import Move, zoneClose, Line, Curve
from inkex.localization import inkex_gettext as _
from inkex.utils import circular_pairwise
class QRLengthError(Exception):
def __init__(self, message):
self.message = message
class QRCode(object):
PAD0 = 0xEC
PAD1 = 0x11
def __init__(self, correction):
self.typeNumber = 1
self.errorCorrectLevel = correction
self.qrDataList = []
self.modules = []
self.moduleCount = 0
def getTypeNumber(self):
return self.typeNumber
def setTypeNumber(self, typeNumber):
self.typeNumber = typeNumber
def clearData(self):
self.qrDataList = []
def addData(self, data):
self.qrDataList.append(data)
def getDataCount(self):
return len(self.qrDataList)
def getData(self, index):
return self.qrDataList[index]
def isDark(self, row, col):
return self.modules[row][col] if self.modules[row][col] is not None else False
def getModuleCount(self):
return self.moduleCount
def make(self):
self._make(False, self._getBestMaskPattern())
def _getBestMaskPattern(self):
minLostPoint = 0
pattern = 0
for i in range(8):
self._make(True, i)
lostPoint = QRUtil.getLostPoint(self)
if i == 0 or minLostPoint > lostPoint:
minLostPoint = lostPoint
pattern = i
return pattern
def _make(self, test, maskPattern):
self.moduleCount = self.typeNumber * 4 + 17
self.modules = np.array(
[[None] * self.moduleCount for i in range(self.moduleCount)]
)
self._setupPositionProbePattern(0, 0)
self._setupPositionProbePattern(self.moduleCount - 7, 0)
self._setupPositionProbePattern(0, self.moduleCount - 7)
self._setupPositionAdjustPattern()
self._setupTimingPattern()
self._setupTypeInfo(test, maskPattern)
if self.typeNumber >= 7:
self._setupTypeNumber(test)
data = QRCode._createData(
self.typeNumber, self.errorCorrectLevel, self.qrDataList
)
self._mapData(data, maskPattern)
def _mapData(self, data, maskPattern):
rows = list(range(self.moduleCount))
cols = [
col - 1 if col <= 6 else col for col in range(self.moduleCount - 1, 0, -2)
]
maskFunc = QRUtil.getMaskFunction(maskPattern)
byteIndex = 0
bitIndex = 7
for col in cols:
rows.reverse()
for row in rows:
for c in range(2):
if self.modules[row][col - c] is None:
dark = False
if byteIndex < len(data):
dark = ((data[byteIndex] >> bitIndex) & 1) == 1
if maskFunc(row, col - c):
dark = not dark
self.modules[row][col - c] = dark
bitIndex -= 1
if bitIndex == -1:
byteIndex += 1
bitIndex = 7
def _setupPositionAdjustPattern(self):
pos = QRUtil.getPatternPosition(self.typeNumber)
for row in pos:
for col in pos:
if self.modules[row][col] is not None:
continue
for r in range(-2, 3):
for c in range(-2, 3):
self.modules[row + r][col + c] = (
r == -2
or r == 2
or c == -2
or c == 2
or (r == 0 and c == 0)
)
def _setupPositionProbePattern(self, row, col):
for r in range(-1, 8):
for c in range(-1, 8):
if (
row + r <= -1
or self.moduleCount <= row + r
or col + c <= -1
or self.moduleCount <= col + c
):
continue
self.modules[row + r][col + c] = (
(0 <= r <= 6 and (c == 0 or c == 6))
or (0 <= c <= 6 and (r == 0 or r == 6))
or (2 <= r <= 4 and 2 <= c <= 4)
)
def _setupTimingPattern(self):
for r in range(8, self.moduleCount - 8):
if self.modules[r][6] is not None:
continue
self.modules[r][6] = r % 2 == 0
for c in range(8, self.moduleCount - 8):
if self.modules[6][c] is not None:
continue
self.modules[6][c] = c % 2 == 0
def _setupTypeNumber(self, test):
bits = QRUtil.getBCHTypeNumber(self.typeNumber)
for i in range(18):
self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = (
not test and ((bits >> i) & 1) == 1
)
for i in range(18):
self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = (
not test and ((bits >> i) & 1) == 1
)
def _setupTypeInfo(self, test, maskPattern):
data = (self.errorCorrectLevel << 3) | maskPattern
bits = QRUtil.getBCHTypeInfo(data)
# vertical
for i in range(15):
mod = not test and ((bits >> i) & 1) == 1
if i < 6:
self.modules[i][8] = mod
elif i < 8:
self.modules[i + 1][8] = mod
else:
self.modules[self.moduleCount - 15 + i][8] = mod
# horizontal
for i in range(15):
mod = not test and ((bits >> i) & 1) == 1
if i < 8:
self.modules[8][self.moduleCount - i - 1] = mod
elif i < 9:
self.modules[8][15 - i - 1 + 1] = mod
else:
self.modules[8][15 - i - 1] = mod
# fixed
self.modules[self.moduleCount - 8][8] = not test
@staticmethod
def _createData(typeNumber, errorCorrectLevel, dataArray):
rsBlocks = RSBlock.getRSBlocks(typeNumber, errorCorrectLevel)
buffer = BitBuffer()
for data in dataArray:
buffer.put(data.getMode(), 4)
buffer.put(data.getLength(), data.getLengthInBits(typeNumber))
data.write(buffer)
totalDataCount = sum(rsBlock.getDataCount() for rsBlock in rsBlocks)
if buffer.getLengthInBits() > totalDataCount * 8:
raise QRLengthError(
"code length overflow. (%s>%s)"
% (buffer.getLengthInBits(), totalDataCount * 8)
)
# end code
if buffer.getLengthInBits() + 4 <= totalDataCount * 8:
buffer.put(0, 4)
# padding
while buffer.getLengthInBits() % 8 != 0:
buffer.put(False, 1)
# padding
while True:
if buffer.getLengthInBits() >= totalDataCount * 8:
break
buffer.put(QRCode.PAD0, 8)
if buffer.getLengthInBits() >= totalDataCount * 8:
break
buffer.put(QRCode.PAD1, 8)
return QRCode._createBytes(buffer, rsBlocks)
@staticmethod
def _createBytes(buffer, rsBlocks):
offset = 0
maxDcCount = 0
maxEcCount = 0
dcdata = [None] * len(rsBlocks)
ecdata = [None] * len(rsBlocks)
for r, b in enumerate(rsBlocks):
dcCount = b.getDataCount()
ecCount = b.getTotalCount() - dcCount
maxDcCount = max(maxDcCount, dcCount)
maxEcCount = max(maxEcCount, ecCount)
dcdata[r] = [0] * dcCount
for i in range(len(dcdata[r])):
dcdata[r][i] = 0xFF & buffer.getBuffer()[i + offset]
offset += dcCount
rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount)
rawPoly = Polynomial(dcdata[r], rsPoly.getLength() - 1)
modPoly = rawPoly.mod(rsPoly)
ecdata[r] = [0] * (rsPoly.getLength() - 1)
for i in range(len(ecdata[r])):
modIndex = i + modPoly.getLength() - len(ecdata[r])
ecdata[r][i] = modPoly.get(modIndex) if modIndex >= 0 else 0
totalCodeCount = sum(rsBlock.getTotalCount() for rsBlock in rsBlocks)
data = [0] * totalCodeCount
index = 0
for i in range(maxDcCount):
for r in range(len(rsBlocks)):
if i < len(dcdata[r]):
data[index] = dcdata[r][i]
index += 1
for i in range(maxEcCount):
for r in range(len(rsBlocks)):
if i < len(ecdata[r]):
data[index] = ecdata[r][i]
index += 1
return data
@staticmethod
def getMinimumQRCode(data, errorCorrectLevel):
qr = QRCode(correction=errorCorrectLevel)
qr.addData(data)
lv = 1
rv = 40
while rv - lv > 0:
mid = (3 * lv + rv) // 4
qr.setTypeNumber(mid)
try:
qr.make()
except QRLengthError:
if mid == 40:
raise inkex.AbortExtension(
_("The string is too large to represent as QR code")
)
lv = mid + 1
else:
rv = mid
qr.setTypeNumber(rv)
qr.make()
return qr
class Mode(object):
MODE_NUMBER = 1 << 0
MODE_ALPHA_NUM = 1 << 1
MODE_8BIT_BYTE = 1 << 2
MODE_KANJI = 1 << 3
class MaskPattern(object):
PATTERN000 = 0
PATTERN001 = 1
PATTERN010 = 2
PATTERN011 = 3
PATTERN100 = 4
PATTERN101 = 5
PATTERN110 = 6
PATTERN111 = 7
class QRUtil(object):
@staticmethod
def getPatternPosition(typeNumber):
return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]
PATTERN_POSITION_TABLE = [
[],
[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],
]
@staticmethod
def getErrorCorrectPolynomial(errorCorrectLength):
a = Polynomial([1])
for i in range(errorCorrectLength):
a = a.multiply(Polynomial([1, QRMath.gexp(i)]))
return a
@staticmethod
def getMaskFunction(maskPattern):
return {
MaskPattern.PATTERN000: lambda i, j: (i + j) % 2 == 0,
MaskPattern.PATTERN001: lambda i, j: i % 2 == 0,
MaskPattern.PATTERN010: lambda i, j: j % 3 == 0,
MaskPattern.PATTERN011: lambda i, j: (i + j) % 3 == 0,
MaskPattern.PATTERN100: lambda i, j: (i // 2 + j // 3) % 2 == 0,
MaskPattern.PATTERN101: lambda i, j: (i * j) % 2 + (i * j) % 3 == 0,
MaskPattern.PATTERN110: lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0,
MaskPattern.PATTERN111: lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0,
}[maskPattern]
@staticmethod
def getLostPoint(qrcode: QRCode):
moduleCount = qrcode.getModuleCount()
lostPoint = 0
other = qrcode.modules.astype("int")
# LEVEL1
same = np.zeros_like(other)
same[:-1, :] += other[:-1, :] == other[1:, :] # east
same[:-1, :-1] += other[:-1, :-1] == other[1:, 1:] # north east
same[:, :-1] += other[:, :-1] == other[:, 1:] # north
same[1:, :-1] += other[1:, :-1] == other[:-1, 1:] # north west
same[1:, :] += other[1:, :] == other[:-1, :] # west
same[1:, 1:] += other[1:, 1:] == other[:-1, :-1] # south west
same[:, 1:] += other[:, 1:] == other[:, :-1] # south
same[:-1, 1:] += other[:-1, 1:] == other[1:, :-1] # south east
lostPoint = np.sum(np.where(same > 5, 3 + same - 5, 0))
# LEVEL2
lostPoint += 3 * np.sum(
np.mod(other[:-1, :-1] + other[1:, :-1] + other[:-1, 1:] + other[1:, 1:], 4)
== 0
)
# LEVEL3
lostPoint += 40 * np.sum(
(other[:, 0:-6] == 1)
& (other[:, 1:-5] == 0)
& (other[:, 2:-4] == 1)
& (other[:, 3:-3] == 1)
& (other[:, 4:-2] == 1)
& (other[:, 5:-1] == 0)
& (other[:, 6:] == 1)
)
lostPoint += 40 * np.sum(
(other[0:-6] == 1)
& (other[1:-5, :] == 0)
& (other[2:-4, :] == 1)
& (other[3:-3, :] == 1)
& (other[4:-2, :] == 1)
& (other[5:-1, :] == 0)
& (other[6:, :] == 1)
)
# LEVEL4
darkCount = np.sum(other)
ratio = abs(100 * darkCount // moduleCount // moduleCount - 50) // 5
lostPoint += ratio * 10
return lostPoint
G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0)
G18 = (
(1 << 12)
| (1 << 11)
| (1 << 10)
| (1 << 9)
| (1 << 8)
| (1 << 5)
| (1 << 2)
| (1 << 0)
)
G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1)
@staticmethod
def getBCHTypeInfo(data):
d = data << 10
while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0:
d ^= QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15))
return ((data << 10) | d) ^ QRUtil.G15_MASK
@staticmethod
def getBCHTypeNumber(data):
d = data << 12
while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0:
d ^= QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18))
return (data << 12) | d
@staticmethod
def getBCHDigit(data):
digit = 0
while data != 0:
digit += 1
data >>= 1
return digit
class QRData(object):
def __init__(self, mode, data):
self._mode = mode
self.data = data
def getMode(self):
return self._mode
def getData(self):
return self.data
def getLength(self):
return len(self.data)
# @abstractmethod
def write(self, buffer):
pass
def getLengthInBits(self, type):
if 1 <= type < 10: # 1 - 9
return {
Mode.MODE_NUMBER: 10,
Mode.MODE_ALPHA_NUM: 9,
Mode.MODE_8BIT_BYTE: 8,
Mode.MODE_KANJI: 8,
}[self._mode]
elif type < 27: # 10 - 26
return {
Mode.MODE_NUMBER: 12,
Mode.MODE_ALPHA_NUM: 11,
Mode.MODE_8BIT_BYTE: 16,
Mode.MODE_KANJI: 10,
}[self._mode]
elif type < 41: # 27 - 40
return {
Mode.MODE_NUMBER: 14,
Mode.MODE_ALPHA_NUM: 13,
Mode.MODE_8BIT_BYTE: 16,
Mode.MODE_KANJI: 12,
}[self._mode]
else:
raise Exception("type:%s" % type)
class QR8BitByte(QRData):
def __init__(self, data):
super(QR8BitByte, self).__init__(Mode.MODE_8BIT_BYTE, data)
if isinstance(data, str):
data = data.encode("ascii", "ignore")
if not isinstance(data, bytes):
raise ValueError("Data must be in bytes!")
def write(self, buffer):
for d in self.data:
buffer.put(ord(d), 8)
class QRAlphaNum(QRData):
def __init__(self, data):
super(QRAlphaNum, self).__init__(Mode.MODE_ALPHA_NUM, data)
def write(self, buffer):
i = 0
while i + 1 < len(self.data):
buffer.put(
QRAlphaNum._getCode(self.data[i]) * 45
+ QRAlphaNum._getCode(self.data[i + 1]),
11,
)
i += 2
if i < len(self.data):
buffer.put(QRAlphaNum._getCode(self.data[i]), 6)
@staticmethod
def _getCode(c):
if "0" <= c and c <= "9":
return ord(c) - ord("0")
elif "A" <= c and c <= "Z":
return ord(c) - ord("A") + 10
else:
dct = {
" ": 36,
"$": 37,
"%": 38,
"*": 39,
"+": 40,
"-": 41,
".": 42,
"/": 43,
":": 44,
}
if c in dct.keys():
return dct[c]
else:
raise inkex.AbortExtension(
_(
"Wrong symbol '{}' in alphanumeric representation: Should be [A-Z, 0-9] or {}"
).format(c, dct.keys())
)
class QRNumber(QRData):
def __init__(self, data):
super(QRNumber, self).__init__(Mode.MODE_NUMBER, data)
def write(self, buffer):
i = 0
try:
while i + 2 < len(self.data):
num = int(self.data[i : i + 3])
buffer.put(num, 10)
i += 3
if i < len(self.data):
ln = 4 if len(self.data) - i == 1 else 7
buffer.put(int(self.data[i:]), ln)
except:
raise ValueError("The string '{}' is not a valid number".format(self.data))
class QRKanji(QRData):
def __init__(self, data):
super(QRKanji, self).__init__(Mode.MODE_KANJI, data)
raise RuntimeError("Class QRKanji is not implemented")
class QRMath(object):
EXP_TABLE = None
LOG_TABLE = None
@staticmethod
def _init():
QRMath.EXP_TABLE = [0] * 256
for i in range(256):
QRMath.EXP_TABLE[i] = (
1 << i
if i < 8
else QRMath.EXP_TABLE[i - 4]
^ QRMath.EXP_TABLE[i - 5]
^ QRMath.EXP_TABLE[i - 6]
^ QRMath.EXP_TABLE[i - 8]
)
QRMath.LOG_TABLE = [0] * 256
for i in range(255):
QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i
@staticmethod
def glog(n):
return QRMath.LOG_TABLE[n]
@staticmethod
def gexp(n):
return QRMath.EXP_TABLE[n % 255]
# initialize statics
QRMath._init()
class Polynomial(object):
def __init__(self, num, shift=0):
offset = 0
length = len(num)
while offset < length and num[offset] == 0:
offset += 1
self.num = num[offset:] + [0] * shift
def get(self, index):
return self.num[index]
def getLength(self):
return len(self.num)
def __repr__(self):
return ",".join([str(self.get(i)) for i in range(self.getLength())])
def toLogString(self):
return ",".join(
[str(QRMath.glog(self.get(i))) for i in range(self.getLength())]
)
def multiply(self, e):
num = [0] * (self.getLength() + e.getLength() - 1)
for i in range(self.getLength()):
for j in range(e.getLength()):
num[i + j] ^= QRMath.gexp(
QRMath.glog(self.get(i)) + QRMath.glog(e.get(j))
)
return Polynomial(num)
def mod(self, e):
if self.getLength() - e.getLength() < 0:
return self
ratio = QRMath.glog(self.get(0)) - QRMath.glog(e.get(0))
num = self.num[:]
for i in range(e.getLength()):
num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio)
return Polynomial(num).mod(e)
class RSBlock(object):
RS_BLOCK_TABLE = [
# L
# M
# Q
# H
# 1
[1, 26, 19],
[1, 26, 16],
[1, 26, 13],
[1, 26, 9],
# 2
[1, 44, 34],
[1, 44, 28],
[1, 44, 22],
[1, 44, 16],
# 3
[1, 70, 55],
[1, 70, 44],
[2, 35, 17],
[2, 35, 13],
# 4
[1, 100, 80],
[2, 50, 32],
[2, 50, 24],
[4, 25, 9],
# 5
[1, 134, 108],
[2, 67, 43],
[2, 33, 15, 2, 34, 16],
[2, 33, 11, 2, 34, 12],
# 6
[2, 86, 68],
[4, 43, 27],
[4, 43, 19],
[4, 43, 15],
# 7
[2, 98, 78],
[4, 49, 31],
[2, 32, 14, 4, 33, 15],
[4, 39, 13, 1, 40, 14],
# 8
[2, 121, 97],
[2, 60, 38, 2, 61, 39],
[4, 40, 18, 2, 41, 19],
[4, 40, 14, 2, 41, 15],
# 9
[2, 146, 116],
[3, 58, 36, 2, 59, 37],
[4, 36, 16, 4, 37, 17],
[4, 36, 12, 4, 37, 13],
# 10
[2, 86, 68, 2, 87, 69],
[4, 69, 43, 1, 70, 44],
[6, 43, 19, 2, 44, 20],
[6, 43, 15, 2, 44, 16],
# 11
[4, 101, 81],
[1, 80, 50, 4, 81, 51],
[4, 50, 22, 4, 51, 23],
[3, 36, 12, 8, 37, 13],
# 12
[2, 116, 92, 2, 117, 93],
[6, 58, 36, 2, 59, 37],
[4, 46, 20, 6, 47, 21],
[7, 42, 14, 4, 43, 15],
# 13
[4, 133, 107],
[8, 59, 37, 1, 60, 38],
[8, 44, 20, 4, 45, 21],
[12, 33, 11, 4, 34, 12],
# 14
[3, 145, 115, 1, 146, 116],
[4, 64, 40, 5, 65, 41],
[11, 36, 16, 5, 37, 17],
[11, 36, 12, 5, 37, 13],
# 15
[5, 109, 87, 1, 110, 88],
[5, 65, 41, 5, 66, 42],
[5, 54, 24, 7, 55, 25],
[11, 36, 12, 7, 37, 13],
# 16
[5, 122, 98, 1, 123, 99],
[7, 73, 45, 3, 74, 46],
[15, 43, 19, 2, 44, 20],
[3, 45, 15, 13, 46, 16],
# 17
[1, 135, 107, 5, 136, 108],
[10, 74, 46, 1, 75, 47],
[1, 50, 22, 15, 51, 23],
[2, 42, 14, 17, 43, 15],
# 18
[5, 150, 120, 1, 151, 121],
[9, 69, 43, 4, 70, 44],
[17, 50, 22, 1, 51, 23],
[2, 42, 14, 19, 43, 15],
# 19
[3, 141, 113, 4, 142, 114],
[3, 70, 44, 11, 71, 45],
[17, 47, 21, 4, 48, 22],
[9, 39, 13, 16, 40, 14],
# 20
[3, 135, 107, 5, 136, 108],
[3, 67, 41, 13, 68, 42],
[15, 54, 24, 5, 55, 25],
[15, 43, 15, 10, 44, 16],
# 21
[4, 144, 116, 4, 145, 117],
[17, 68, 42],
[17, 50, 22, 6, 51, 23],
[19, 46, 16, 6, 47, 17],
# 22
[2, 139, 111, 7, 140, 112],
[17, 74, 46],
[7, 54, 24, 16, 55, 25],
[34, 37, 13],
# 23
[4, 151, 121, 5, 152, 122],
[4, 75, 47, 14, 76, 48],
[11, 54, 24, 14, 55, 25],
[16, 45, 15, 14, 46, 16],
# 24
[6, 147, 117, 4, 148, 118],
[6, 73, 45, 14, 74, 46],
[11, 54, 24, 16, 55, 25],
[30, 46, 16, 2, 47, 17],
# 25
[8, 132, 106, 4, 133, 107],
[8, 75, 47, 13, 76, 48],
[7, 54, 24, 22, 55, 25],
[22, 45, 15, 13, 46, 16],
# 26
[10, 142, 114, 2, 143, 115],
[19, 74, 46, 4, 75, 47],
[28, 50, 22, 6, 51, 23],
[33, 46, 16, 4, 47, 17],
# 27
[8, 152, 122, 4, 153, 123],
[22, 73, 45, 3, 74, 46],
[8, 53, 23, 26, 54, 24],
[12, 45, 15, 28, 46, 16],
# 28
[3, 147, 117, 10, 148, 118],
[3, 73, 45, 23, 74, 46],
[4, 54, 24, 31, 55, 25],
[11, 45, 15, 31, 46, 16],
# 29
[7, 146, 116, 7, 147, 117],
[21, 73, 45, 7, 74, 46],
[1, 53, 23, 37, 54, 24],
[19, 45, 15, 26, 46, 16],
# 30
[5, 145, 115, 10, 146, 116],
[19, 75, 47, 10, 76, 48],
[15, 54, 24, 25, 55, 25],
[23, 45, 15, 25, 46, 16],
# 31
[13, 145, 115, 3, 146, 116],
[2, 74, 46, 29, 75, 47],
[42, 54, 24, 1, 55, 25],
[23, 45, 15, 28, 46, 16],
# 32
[17, 145, 115],
[10, 74, 46, 23, 75, 47],
[10, 54, 24, 35, 55, 25],
[19, 45, 15, 35, 46, 16],
# 33
[17, 145, 115, 1, 146, 116],
[14, 74, 46, 21, 75, 47],
[29, 54, 24, 19, 55, 25],
[11, 45, 15, 46, 46, 16],
# 34
[13, 145, 115, 6, 146, 116],
[14, 74, 46, 23, 75, 47],
[44, 54, 24, 7, 55, 25],
[59, 46, 16, 1, 47, 17],
# 35
[12, 151, 121, 7, 152, 122],
[12, 75, 47, 26, 76, 48],
[39, 54, 24, 14, 55, 25],
[22, 45, 15, 41, 46, 16],
# 36
[6, 151, 121, 14, 152, 122],
[6, 75, 47, 34, 76, 48],
[46, 54, 24, 10, 55, 25],
[2, 45, 15, 64, 46, 16],
# 37
[17, 152, 122, 4, 153, 123],
[29, 74, 46, 14, 75, 47],
[49, 54, 24, 10, 55, 25],
[24, 45, 15, 46, 46, 16],
# 38
[4, 152, 122, 18, 153, 123],
[13, 74, 46, 32, 75, 47],
[48, 54, 24, 14, 55, 25],
[42, 45, 15, 32, 46, 16],
# 39
[20, 147, 117, 4, 148, 118],
[40, 75, 47, 7, 76, 48],
[43, 54, 24, 22, 55, 25],
[10, 45, 15, 67, 46, 16],
# 40
[19, 148, 118, 6, 149, 119],
[18, 75, 47, 31, 76, 48],
[34, 54, 24, 34, 55, 25],
[20, 45, 15, 61, 46, 16],
]
def __init__(self, totalCount, dataCount):
self.totalCount = totalCount
self.dataCount = dataCount
def getDataCount(self):
return self.dataCount
def getTotalCount(self):
return self.totalCount
def __repr__(self):
return "(total=%s,data=%s)" % (self.totalCount, self.dataCount)
@staticmethod
def getRSBlocks(typeNumber, errorCorrectLevel):
rsBlock = RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel)
length = len(rsBlock) // 3
list = []
for i in range(length):
count = rsBlock[i * 3 + 0]
totalCount = rsBlock[i * 3 + 1]
dataCount = rsBlock[i * 3 + 2]
list += [RSBlock(totalCount, dataCount)] * count
return list
@staticmethod
def getRsBlockTable(typeNumber, errorCorrectLevel):
return {
1: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0],
0: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1],
3: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2],
2: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3],
}[errorCorrectLevel]
class BitBuffer(object):
def __init__(self, inclements=32):
self.inclements = inclements
self.buffer = [0] * self.inclements
self.length = 0
def getBuffer(self):
return self.buffer
def getLengthInBits(self):
return self.length
def get(self, index):
return ((self.buffer[index // 8] >> (7 - index % 8)) & 1) == 1
def putBit(self, bit):
if self.length == len(self.buffer) * 8:
self.buffer += [0] * self.inclements
if bit:
self.buffer[self.length // 8] |= 0x80 >> (self.length % 8)
self.length += 1
def put(self, num, length):
for i in range(length):
self.putBit(((num >> (length - i - 1)) & 1) == 1)
def __repr__(self):
return "".join(
"1" if self.get(i) else "0" for i in range(self.getLengthInBits())
)
class GridDrawer(object):
"""Mechanism to draw grids of boxes"""
def __init__(self, invert_code, smooth_factor):
self.invert_code = invert_code
self.smoothFactor = smooth_factor
self.grid = None
self.row_count = 0
self.col_count = 0
def set_grid(self, grid):
if len({len(g) for g in grid}) != 1:
raise Exception("The array is not rectangular")
else:
self.grid = grid
self.row_count = len(self.grid)
self.col_count = len(self.grid[0])
def isDark(self, col, row):
inside = 0 <= col < self.col_count and 0 <= row < self.row_count
return False if not inside else self.grid[row][col] != self.invert_code
dm = {0: (1, 0), 1: (0, -1), 2: (-1, 0), 3: (0, 1)}
@staticmethod
def moveByDirection(xyd):
return xyd[0] + GridDrawer.dm[xyd[2]][0], xyd[1] + GridDrawer.dm[xyd[2]][1]
@staticmethod
def makeDirectionsTable():
result = []
for cfg in product(range(2), repeat=4):
result.append([])
for d in range(4):
if cfg[3 - d] == 0 and cfg[3 - (d - 1) % 4] != 0:
result[-1].append(d)
return result
def createVertexesForAdvDrawer(self):
dirTable = self.makeDirectionsTable()
result = []
# Create vertex
for row in range(self.row_count + 1):
for col in range(self.col_count + 1):
indx = (
(2**0 if self.isDark(col - 0, row - 1) else 0)
+ (2**1 if self.isDark(col - 1, row - 1) else 0)
+ (2**2 if self.isDark(col - 1, row - 0) else 0)
+ (2**3 if self.isDark(col - 0, row - 0) else 0)
)
for d in dirTable[indx]:
result.append((col, row, d, len(dirTable[indx]) > 1))
return result
def getSmoothPosition(self, v, extraSmoothFactor=1.0):
vn = self.moveByDirection(v)
sc = extraSmoothFactor * self.smoothFactor / 2.0
sc1 = 1.0 - sc
return (v[0] * sc1 + vn[0] * sc, v[1] * sc1 + vn[1] * sc), (
v[0] * sc + vn[0] * sc1,
v[1] * sc + vn[1] * sc1,
)
class QrCode(inkex.GenerateExtension):
"""Generate QR Code Extension"""
def add_arguments(self, pars):
pars.add_argument("--text", default="https://inkscape.org")
pars.add_argument("--typenumber", type=int, default=0)
pars.add_argument("--correctionlevel", type=int, default=0)
pars.add_argument("--qrmode", type=int, default=0)
pars.add_argument("--encoding", default="latin_1")
pars.add_argument("--modulesize", type=float, default=4.0)
pars.add_argument("--invert", type=inkex.Boolean, default="false")
pars.add_argument(
"--drawtype",
default="smooth",
choices=["smooth", "pathpreset", "selection", "symbol"],
)
pars.add_argument(
"--smoothness", default="neutral", choices=["neutral", "greedy", "proud"]
)
pars.add_argument("--pathtype", default="simple", choices=["simple", "circle"])
pars.add_argument("--smoothval", type=float, default=0.2)
pars.add_argument("--symbolid", default="")
pars.add_argument("--groupid", default="")
def generate(self):
scale = self.svg.unittouu("1px") # convert to document units
opt = self.options
if not opt.text:
raise inkex.AbortExtension(_("Please enter an input text"))
elif opt.drawtype == "symbol" and opt.symbolid == "":
raise inkex.AbortExtension(_("Please enter symbol id"))
# for Python 3 ugly hack to represent bytes as str for Python2 compatibility
text_str = str(opt.text)
text_in = opt.text.replace("\\n", "\n")
cmode = [QR8BitByte, QRNumber, QRAlphaNum, QRKanji][opt.qrmode]
text_data = cmode(bytes(text_in, opt.encoding).decode("latin_1"))
grp = Group()
grp.set("inkscape:label", "QR Code: " + text_str)
if opt.groupid:
grp.set("id", opt.groupid)
pos_x, pos_y = self.svg.namedview.center
grp.transform.add_translate(pos_x, pos_y)
if scale:
grp.transform.add_scale(scale)
# GENERATE THE QRCODE
if opt.typenumber == 0:
# Automatic QR code size`
code = QRCode.getMinimumQRCode(text_data, opt.correctionlevel)
else:
# Manual QR code size
code = QRCode(correction=opt.correctionlevel)
code.setTypeNumber(int(opt.typenumber))
code.addData(text_data)
code.make()
self.boxsize = opt.modulesize
self.invert_code = opt.invert
self.margin = 4
self.draw = GridDrawer(opt.invert, opt.smoothval)
self.draw.set_grid(code.modules)
self.render_svg(grp, opt.drawtype)
return grp
def ring_iterator(self, vertices, greedy):
visited = set()
lut = defaultdict(list)
for i, v in enumerate(vertices):
lut[v[0:2]].append(i)
def getnext(vertex):
nextPos = self.draw.moveByDirection(vertex)
nextIndexes = lut[nextPos[0:2]]
if len(nextIndexes) == 0 or len(nextIndexes) > 2:
raise Exception("Vertex " + str(next_c) + " has no connections")
elif len(nextIndexes) == 1:
vertsIndexNext = nextIndexes[0]
else:
if {vertices[nextIndexes[0]][2], vertices[nextIndexes[1]][2]} != {
(vertex[2] - 1) % 4,
(vertex[2] + 1) % 4,
}:
raise Exception(
"Bad next vertex directions "
+ str(vertices[nextIndexes[0]])
+ str(vertices[nextIndexes[1]])
)
# Greedy - CCW turn, proud and neutral CW turn
vertsIndexNext = (
nextIndexes[0]
if (greedy == "g")
== (vertices[nextIndexes[0]][2] == (vertex[2] + 1) % 4)
else nextIndexes[1]
)
return vertices[vertsIndexNext]
for start_vertex in reversed(vertices):
if start_vertex in visited:
continue
path = [start_vertex]
next_vertex = getnext(start_vertex)
while start_vertex != next_vertex:
path.append(next_vertex)
next_vertex = getnext(next_vertex)
else:
yield path
visited |= set(path)
def render_adv(self, greedy):
verts = self.draw.createVertexesForAdvDrawer()
it = self.ring_iterator(verts, greedy)
qrPath = inkex.Path()
for ringVertices in it:
posStart, _ = self.draw.getSmoothPosition(ringVertices[0])
qrPath.append(Move(*self.get_svg_pos(posStart[0], posStart[1])))
for vc, vn in circular_pairwise(ringVertices):
if vn[2] != vc[2]:
if (greedy != "n") or not vn[3]:
# Add bezier
# Opt length http://spencermortensen.com/articles/bezier-circle/
# c = 0.552284749
ex = 1 - 0.552284749
_, bs = self.draw.getSmoothPosition(vc)
_, bp1 = self.draw.getSmoothPosition(vc, ex)
bp2, _ = self.draw.getSmoothPosition(vn, ex)
bf, _ = self.draw.getSmoothPosition(vn)
qrPath.append(Line(*self.get_svg_pos(bs[0], bs[1])))
qrPath.append(
Curve(
*self.get_svg_pos(bp1[0], bp1[1]),
*self.get_svg_pos(bp2[0], bp2[1]),
*self.get_svg_pos(bf[0], bf[1]),
)
)
else:
# Add straight
qrPath.append(Line(*self.get_svg_pos(vn[0], vn[1])))
qrPath.append(zoneClose())
return PathElement.new(path=qrPath)
def render_obsolete(self):
for row in range(self.draw.row_count):
for col in range(self.draw.col_count):
if self.draw.isDark(col, row):
x, y = self.get_svg_pos(col, row)
return Rectangle.new(x, y, self.boxsize, self.boxsize)
def render_path(self, pointStr):
singlePath = inkex.Path(self.get_icon_path_str(pointStr))
path = inkex.Path()
for row in range(self.draw.row_count):
for col in range(self.draw.col_count):
if self.draw.isDark(col, row):
x, y = self.get_svg_pos(col, row)
path.append(Move(x, y))
path += singlePath
path.append(zoneClose())
return PathElement.new(path=path)
def render_selection(self):
if len(self.svg.selection) > 0:
self.options.symbolid = self.svg.selection.first().get_id()
else:
raise inkex.AbortExtension(_("Please select an element to clone"))
return self.render_symbol()
def render_symbol(self):
symbol = self.svg.getElementById(self.options.symbolid)
if symbol is None:
raise inkex.AbortExtension(
_("Can't find symbol {}").format(self.options.symbolid)
)
bbox = symbol.path.bounding_box()
transform = inkex.Transform(
scale=(
float(self.boxsize) / bbox.width,
float(self.boxsize) / bbox.height,
)
)
result = Group()
for row in range(self.draw.row_count):
for col in range(self.draw.col_count):
if self.draw.isDark(col, row):
x, y = self.get_svg_pos(col, row)
# Inkscape doesn't support width/height on use tags
result.append(
Use.new(
symbol,
x / transform.a,
y / transform.d,
transform=transform,
)
)
return result
def render_pathpreset(self):
if self.options.pathtype == "simple":
return self.render_path("h 1 v 1 h -1")
else:
s = (
"m 0.5,0.5 "
"c 0.2761423745,0 0.5,0.2238576255 0.5,0.5 "
"c 0,0.2761423745 -0.2238576255,0.5 -0.5,0.5 "
"c -0.2761423745,0 -0.5,-0.2238576255 -0.5,-0.5 "
"c 0,-0.2761423745 0.2238576255,-0.5 0.5,-0.5"
)
return self.render_path(s)
render_smooth = lambda self: self.render_adv(self.options.smoothness[0])
def render_svg(self, grp, drawtype):
"""Render to svg"""
drawer = getattr(self, f"render_{drawtype}", self.render_obsolete)
if drawer is None:
raise Exception("Unknown draw type: " + drawtype)
canvas_width = (self.draw.col_count + 2 * self.margin) * self.boxsize
canvas_height = (self.draw.row_count + 2 * self.margin) * self.boxsize
# white background providing margin:
rect = grp.add(Rectangle.new(0, 0, canvas_width, canvas_height))
rect.style["stroke"] = "none"
rect.style["fill"] = "black" if self.invert_code else "white"
qrg = grp.add(Group())
qrg.style["stroke"] = "none"
qrg.style["fill"] = "white" if self.invert_code else "black"
qrg.add(drawer())
def get_svg_pos(self, col, row):
return (col + self.margin) * self.boxsize, (row + self.margin) * self.boxsize
def get_icon_path_str(self, pointStr):
result = ""
digBuffer = ""
for c in pointStr:
if c.isdigit() or c == "-" or c == ".":
digBuffer += c
else:
if len(digBuffer) > 0:
result += str(float(digBuffer) * self.boxsize)
digBuffer = ""
result += c
if len(digBuffer) > 0:
result += str(float(digBuffer) * self.boxsize)
return result
if __name__ == "__main__":
QrCode().run()