diff --git a/README.md b/README.md
index 121ea63e..707b1221 100644
--- a/README.md
+++ b/README.md
@@ -3000,6 +3000,7 @@ force use of system modules instead of the vendored versions:
| -------------------- | ------------ |
| `PRTY_SYS_ALL` | all of the below |
| `PRTY_SYS_IFADDR` | replace [stolen/ifaddr](./copyparty/stolen/ifaddr) with [upstream](https://pypi.org/project/ifaddr/) |
+| `PRTY_SYS_QRCG` | replace [stolen/qrcodegen.py](./copyparty/stolen/qrcodegen.py) with [upstream](https://github.com/nayuki/QR-Code-generator/blob/master/python/qrcodegen.py) |
to debug, run copyparty with `PRTY_MODSPEC=1` to see where it's getting each module from
diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py
index 77778547..1638f892 100644
--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -34,8 +34,8 @@ from .__init__ import ANYWIN, RES, TYPE_CHECKING, EnvParams, unicode
from .__version__ import S_VERSION
from .authsrv import LEELOO_DALLAS, VFS # typechk
from .bos import bos
+from .qrkode import QrCode, qr2svg, qrgen
from .star import StreamTar
-from .stolen.qrcodegen import QrCode, qr2svg
from .sutil import StreamArc, gfilter
from .szip import StreamZip
from .up2k import up2k_chunksize
@@ -4951,7 +4951,7 @@ class HttpCli(object):
url += "#" + uhash
self.log("qrcode(%r)" % (url,))
- ret = qr2svg(QrCode.encode_binary(url.encode("utf-8")), 2)
+ ret = qr2svg(qrgen(url.encode("utf-8")), 2)
self.reply(ret.encode("utf-8"), mime="image/svg+xml")
return True
diff --git a/copyparty/qrkode.py b/copyparty/qrkode.py
new file mode 100644
index 00000000..34ebbcca
--- /dev/null
+++ b/copyparty/qrkode.py
@@ -0,0 +1,112 @@
+# coding: utf-8
+from __future__ import print_function, unicode_literals
+
+import os
+
+try:
+ if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_QRCG"):
+ raise ImportError()
+ from .stolen.qrcodegen import QrCode
+
+ qrgen = QrCode.encode_binary
+ VENDORED = True
+except ImportError:
+ VENDORED = False
+ from qrcodegen import QrCode
+
+if os.getenv("PRTY_MODSPEC"):
+ from inspect import getsourcefile
+
+ print("PRTY_MODSPEC: qrcode:", getsourcefile(QrCode))
+
+if True: # pylint: disable=using-constant-test
+ import typing
+ from typing import Any, Optional, Sequence, Union
+
+
+if not VENDORED:
+
+ def _qrgen(data: Union[bytes, Sequence[int]]) -> "QrCode":
+ ret = None
+ V = QrCode.Ecc
+ for e in [V.HIGH, V.QUARTILE, V.MEDIUM, V.LOW]:
+ qr = QrCode.encode_binary(data, e)
+ qr.size = qr._size
+ qr.modules = qr._modules
+ if not ret or ret.size > qr.size:
+ ret = qr
+ return ret
+
+ qrgen = _qrgen
+
+
+def qr2txt(qr: QrCode, zoom: int = 1, pad: int = 4) -> str:
+ tab = qr.modules
+ sz = qr.size
+ if sz % 2 and zoom == 1:
+ tab.append([False] * sz)
+
+ tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
+ tab = [[False] * pad + x + [False] * pad for x in tab]
+
+ rows: list[str] = []
+ if zoom == 1:
+ for y in range(0, len(tab), 2):
+ row = ""
+ for x in range(len(tab[y])):
+ v = 2 if tab[y][x] else 0
+ v += 1 if tab[y + 1][x] else 0
+ row += " ▄▀█"[v]
+ rows.append(row)
+ else:
+ for tr in tab:
+ row = ""
+ for zb in tr:
+ row += " █"[int(zb)] * 2
+ rows.append(row)
+
+ return "\n".join(rows)
+
+
+def qr2png(
+ qr: QrCode,
+ zoom: int,
+ pad: int,
+ bg: Optional[tuple[int, int, int]],
+ fg: Optional[tuple[int, int, int]],
+ ap: str,
+) -> None:
+ from PIL import Image
+
+ tab = qr.modules
+ sz = qr.size
+ psz = sz + pad * 2
+ if bg:
+ img = Image.new("RGB", (psz, psz), bg)
+ else:
+ img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
+ fg = (fg[0], fg[1], fg[2], 255)
+ for y in range(sz):
+ for x in range(sz):
+ if tab[y][x]:
+ img.putpixel((x + pad, y + pad), fg)
+ if zoom != 1:
+ img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
+ img.save(ap)
+
+
+def qr2svg(qr: QrCode, border: int) -> str:
+ parts: list[str] = []
+ for y in range(qr.size):
+ sy = border + y
+ for x in range(qr.size):
+ if qr.modules[y][x]:
+ parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
+ t = """\
+
+
+"""
+ return t.format(qr.size + border * 2, " ".join(parts))
diff --git a/copyparty/stolen/qrcodegen.py b/copyparty/stolen/qrcodegen.py
index 8790e859..937dc88e 100644
--- a/copyparty/stolen/qrcodegen.py
+++ b/copyparty/stolen/qrcodegen.py
@@ -4,7 +4,7 @@
# https://github.com/nayuki/QR-Code-generator/blob/daa3114/python/qrcodegen.py
# the original ^ is extremely well commented so refer to that for explanations
-# hacks: binary-only, auto-ecc, render, py2-compat
+# hacks: binary-only, auto-ecc, py2-compat
from __future__ import print_function, unicode_literals
@@ -173,52 +173,6 @@ class QrCode(object):
self._apply_mask(msk) # Apply the final choice of mask
self._draw_format_bits(msk) # Overwrite old format bits
- def render(self, zoom=1, pad=4) -> str:
- tab = self.modules
- sz = self.size
- if sz % 2 and zoom == 1:
- tab.append([False] * sz)
-
- tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
- tab = [[False] * pad + x + [False] * pad for x in tab]
-
- rows: list[str] = []
- if zoom == 1:
- for y in range(0, len(tab), 2):
- row = ""
- for x in range(len(tab[y])):
- v = 2 if tab[y][x] else 0
- v += 1 if tab[y + 1][x] else 0
- row += " ▄▀█"[v]
- rows.append(row)
- else:
- for tr in tab:
- row = ""
- for zb in tr:
- row += " █"[int(zb)] * 2
- rows.append(row)
-
- return "\n".join(rows)
-
- def to_png(self, zoom, pad, bg, fg, ap) -> None:
- from PIL import Image
-
- tab = self.modules
- sz = self.size
- psz = sz + pad * 2
- if bg:
- img = Image.new("RGB", (psz, psz), bg)
- else:
- img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
- fg = (fg[0], fg[1], fg[2], 255)
- for y in range(sz):
- for x in range(sz):
- if tab[y][x]:
- img.putpixel((x + pad, y + pad), fg)
- if zoom != 1:
- img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
- img.save(ap)
-
def _draw_function_patterns(self) -> None:
# Draw horizontal and vertical timing patterns
for i in range(self.size):
@@ -613,20 +567,3 @@ def _get_bit(x: int, i: int) -> bool:
class DataTooLongError(ValueError):
pass
-
-
-def qr2svg(qr: QrCode, border: int) -> str:
- parts: list[str] = []
- for y in range(qr.size):
- sy = border + y
- for x in range(qr.size):
- if qr.modules[y][x]:
- parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
- t = """\
-
-
-"""
- return t.format(qr.size + border * 2, " ".join(parts))
diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py
index 72638d23..a68bf376 100644
--- a/copyparty/tcpsrv.py
+++ b/copyparty/tcpsrv.py
@@ -9,7 +9,7 @@ import time
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
from .cert import gencert
-from .stolen.qrcodegen import QrCode, qr2svg
+from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
from .util import (
E_ACCESS,
E_ADDR_IN_USE,
@@ -629,7 +629,7 @@ class TcpSrv(object):
pad = self.args.qrp
zoom = self.args.qrz
- qrc = QrCode.encode_binary(btxt)
+ qrc = qrgen(btxt)
for zs in self.args.qr_file or []:
self._qr2file(qrc, zs)
@@ -642,7 +642,7 @@ class TcpSrv(object):
except:
zoom = 1
- qr = qrc.render(zoom, pad)
+ qr = qr2txt(qrc, zoom, pad)
if self.args.no_ansi:
return "{}\n{}".format(txt, qr)
@@ -683,12 +683,12 @@ class TcpSrv(object):
if zoom not in (1, 2):
raise Exception("invalid zoom for qr.txt; must be 1 or 2")
with open(ap, "wb") as f:
- f.write(qrc.render(zoom, pad).encode("utf-8"))
+ f.write(qr2txt(qrc, zoom, pad).encode("utf-8"))
elif ap.endswith(".svg"):
with open(ap, "wb") as f:
f.write(qr2svg(qrc, pad).encode("utf-8"))
else:
- qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
+ qr2png(qrc, zoom, pad, self._h2i(bg), self._h2i(fg), ap)
def _h2i(self, hs):
try:
diff --git a/scripts/sfx.ls b/scripts/sfx.ls
index 66bcf9df..796d7d21 100644
--- a/scripts/sfx.ls
+++ b/scripts/sfx.ls
@@ -25,6 +25,7 @@ copyparty/metrics.py,
copyparty/mtag.py,
copyparty/multicast.py,
copyparty/pwhash.py,
+copyparty/qrkode.py,
copyparty/res,
copyparty/res/__init__.py,
copyparty/res/COPYING.txt,