From 993a5f542ecf20015f7201ce6228d59555a07a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 15:26:05 +0100 Subject: [PATCH 01/15] Add additional tests. 1. Test function patterns survive encoding 2. Test format patterns 3. Test version pattern --- qrencode-tests.el | 94 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index b7300a2..61ad7d4 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -42,13 +42,14 @@ (should (= (qrencode--field-exp field 1) 2)))) (ert-deftest qrencode-ecc-test () + "Compare ECC encoding against known value." (let ((data [#x10 #x20 #x0c #x56 #x61 #x80 #xec #x11 #xec #x11 #xec #x11 #xec #x11 #xec #x11]) (check [#xa5 #x24 #xd4 #xc1 #xed #x36 #xc7 #x87 #x2c #x55])) (should (equal (qrencode--ecc data (length check)) check)))) (ert-deftest qrencode-ecc-linear-test () (let ((field (qrencode--init-field #x11d 2))) - + (should (equal (qrencode--ecc [#x00 #x00] 2 field) [#x00 #x00])) (let* ((c1 (qrencode--ecc [#x00 #x01] 2 field)) @@ -60,6 +61,7 @@ ;;; Util (ert-deftest qrencode-size-test () + "Verify known sizes." (should (= (qrencode--size 1) 21)) (should (= (qrencode--size 2) 25)) (should (= (qrencode--size 6) 41)) @@ -129,7 +131,8 @@ (should (= (qrencode--aaref s 6 11) 0)))) (ert-deftest qrencode-template-test () - (pcase-let ((`(,qr . ,fp) (qrencode--template 1))) ; TODO: Maybe test a version with alignment pattern + "Test basic templates." + (pcase-let ((`(,qr . ,fp) (qrencode--template 1))) ; TODO: Maybe test a version with alignment pattern (should (equal qr [[1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1] [1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1] [1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1] @@ -264,8 +267,56 @@ ;; TODO: test find-best-mask +(ert-deftest qrencode-function-patterns-intact-test () + "Finder, separator, timing and alignment modules must survive encoding." + (dolist (input '("hello" "https://github.com/ruediger/qrencode-el")) + (let* ((qr (qrencode input nil nil 'return-raw)) + (size (length qr)) + (version (/ (- size 17) 4))) + (pcase-let ((`(,template . ,fp) (qrencode--template version))) + (dotimes (y size) + (dotimes (x size) + ;; Skip the areas written after templating: format info, version + ;; info and the dark module. + (unless (or (= x 8) (= y 8) + (and (>= version 7) + (or (and (< x 6) (>= y (- size 11)) (< y (- size 8))) + (and (< y 6) (>= x (- size 11)) (< x (- size 8)))))) + (when (= 1 (qrencode--aaref fp x y)) + (should (= (qrencode--aaref qr x y) + (qrencode--aaref template x y))))))) + ;; Dark module, section 7.9.1. + (should (= 1 (qrencode--aaref qr 8 (- size 8)))))))) + ;;; Version/Info encoding +;; Helper functions +(defun qrencode-tests--format-bits (qr position) + "Return the 15-bit format information word read from QR. +POSITION is `top-left' for the top-left copy, `bottom+right' for the split +copy along the right and bottom edges." + (let ((size (length qr)) + (v 0)) + (dotimes (i 15) + (let ((bit (if (eq position 'top-left) + (cond ((<= i 5) (qrencode--aaref qr 8 i)) + ((= i 6) (qrencode--aaref qr 8 7)) + ((= i 7) (qrencode--aaref qr 8 8)) + ((= i 8) (qrencode--aaref qr 7 8)) + (t (qrencode--aaref qr (- 14 i) 8))) + (if (<= i 7) + (qrencode--aaref qr (- size 1 i) 8) + (qrencode--aaref qr 8 (+ (- size 7) (- i 8))))))) + (setq v (logior v (ash bit i))))) + v)) + +(defun qrencode-tests--decode-format (qr) + "Return (ERRCORR . MASK) decoded from QR's format information." + (let* ((raw (qrencode-tests--format-bits qr 1)) + (data (ash (logxor raw #x5412) -10))) + (cons (car (rassq (ash data -3) '((L . 1) (M . 0) (Q . 3) (H . 2)))) + (logand data 7)))) + (ert-deftest qrencode-bch-encode-test () ;; Section 7.9.1. Err corr: M, Mask 5 (101) -> 0b100000011001110 (should (= (qrencode--bch-encode #x5) #x40CE))) @@ -277,7 +328,42 @@ (ert-deftest qrencode--mod-test () (should (= (qrencode--mod 0 #x537) 0))) -;; TODO test: encode-info encode-version +(ert-deftest qrencode-format-info-conformance-test () + "Both copies of the format information must agree and be a valid BCH word." + (dolist (input '("hello" + "https://github.com/ruediger/qrencode-el" + "0123456789" + "x")) + (let* ((qr (qrencode input nil nil 'return-raw)) + (c1 (qrencode-tests--format-bits qr 'top-left)) + (c2 (qrencode-tests--format-bits qr 'bottom+right))) + ;; The two copies are redundant: they must be identical. + (should (= c1 c2)) + ;; It must be a well-formed BCH(15,5) word masked with 0x5412. + (pcase-let ((`(,ec . ,mask) (qrencode-tests--decode-format qr))) + (should (memq ec '(L M Q H))) + (should (<= 0 mask 7)) + (should (= c1 (qrencode--bch-encode + (logior (ash (qrencode--errcorr ec) 3) mask)))))))) + + (ert-deftest qrencode-version-info-conformance-test () + "Version information must be present, doubled and correct for version >= 7. + The expected word for version 7 is the literal from section 7.10 of the + standard, so this does not lean on `qrencode--version-ecc' as its own oracle." + (dolist (n '(150 271 900)) + (let* ((qr (qrencode (make-string n ?a) nil nil 'return-raw)) + (size (length qr)) + (version (/ (- size 17) 4)) + (bl 0) (tr 0)) + (should (>= version 7)) + (dotimes (i 18) + (let ((a (/ i 3)) (b (% i 3))) + (setq bl (logior bl (ash (qrencode--aaref qr a (+ (- size 11) b)) i)) + tr (logior tr (ash (qrencode--aaref qr (+ (- size 11) b) a) i))))) + ;; The two copies are redundant: they must be identical. + (should (= bl tr)) + (when (= version 7) + (should (= bl #x07C94)))))) (ert-deftest qrencode--find-version () (should (equal (qrencode--find-version 39 'byte) '(3 . M))) @@ -348,6 +434,8 @@ "))) (ert-deftest qrencode-zbarimg-test () + "Test decoding generated QRCodes using the zbarimg program. +Note: This test will succeed silently if zbarimg is not found." (let ((zbarimg (executable-find "zbarimg"))) (if (null zbarimg) (message "zbarimg not found. Not running all tests!") From 786f37aeb55e6a8fc17a65480dcb1542f5028018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 15:40:45 +0100 Subject: [PATCH 02/15] Fix: length in version calculation. Right order is (/ (+ 4 ccbits) 8). This caused over-reservation of three bytes. Also improved test to cover more versions. --- qrencode-tests.el | 65 ++++++++++++++++++++++++++++++++--------------- qrencode.el | 4 +-- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index 61ad7d4..1382369 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -212,7 +212,7 @@ [0 1 0 1 0 1] [1 1 1 0 1 0] [0 1 0 1 0 1]]) - 3)) + 3)) ;; 1:1:3:1:1 penalty (should (>= (qrencode--penalty [[1 0 1 0 1 0 0 1 0 1 0] [0 0 0 0 1 0 1 1 1 0 1] @@ -346,28 +346,51 @@ copy along the right and bottom edges." (should (= c1 (qrencode--bch-encode (logior (ash (qrencode--errcorr ec) 3) mask)))))))) - (ert-deftest qrencode-version-info-conformance-test () - "Version information must be present, doubled and correct for version >= 7. +(ert-deftest qrencode-version-info-conformance-test () + "Version information must be present, doubled and correct for version >= 7. The expected word for version 7 is the literal from section 7.10 of the standard, so this does not lean on `qrencode--version-ecc' as its own oracle." - (dolist (n '(150 271 900)) - (let* ((qr (qrencode (make-string n ?a) nil nil 'return-raw)) - (size (length qr)) - (version (/ (- size 17) 4)) - (bl 0) (tr 0)) - (should (>= version 7)) - (dotimes (i 18) - (let ((a (/ i 3)) (b (% i 3))) - (setq bl (logior bl (ash (qrencode--aaref qr a (+ (- size 11) b)) i)) - tr (logior tr (ash (qrencode--aaref qr (+ (- size 11) b) a) i))))) - ;; The two copies are redundant: they must be identical. - (should (= bl tr)) - (when (= version 7) - (should (= bl #x07C94)))))) - -(ert-deftest qrencode--find-version () - (should (equal (qrencode--find-version 39 'byte) '(3 . M))) - (should (equal (qrencode--find-version 14 'byte) '(1 . L)))) + (dolist (n '(150 271 900)) + (let* ((qr (qrencode (make-string n ?a) nil nil 'return-raw)) + (size (length qr)) + (version (/ (- size 17) 4)) + (bl 0) (tr 0)) + (should (>= version 7)) + (dotimes (i 18) + (let ((a (/ i 3)) (b (% i 3))) + (setq bl (logior bl (ash (qrencode--aaref qr a (+ (- size 11) b)) i)) + tr (logior tr (ash (qrencode--aaref qr (+ (- size 11) b) a) i))))) + ;; The two copies are redundant: they must be identical. + (should (= bl tr)) + (when (= version 7) + (should (= bl #x07C94)))))) + +(ert-deftest qrencode--find-version-test () + "Selection must match the byte-mode capacities of ISO/IEC 18004 Table 7." + ;; Version 1 holds 17 (L), 14 (M), 11 (Q), 7 (H) bytes. Check each + ;; capacity and the byte that overflows it: an error in the header + ;; overhead shifts every one of these boundaries. + (should (equal (qrencode--find-version 7 'byte) '(1 . H))) + (should (equal (qrencode--find-version 8 'byte) '(1 . Q))) + (should (equal (qrencode--find-version 11 'byte) '(1 . Q))) + (should (equal (qrencode--find-version 12 'byte) '(1 . M))) + (should (equal (qrencode--find-version 14 'byte) '(1 . M))) + (should (equal (qrencode--find-version 15 'byte) '(1 . L))) + (should (equal (qrencode--find-version 17 'byte) '(1 . L))) + (should (equal (qrencode--find-version 18 'byte) '(2 . Q))) + ;; The character count indicator grows from 8 to 16 bits at version 10, + ;; so the overhead goes from 2 to 3 codewords across this boundary. + ;; Table 7: 230 bytes at 9-L, 271 at 10-L. + (should (equal (qrencode--find-version 230 'byte) '(9 . L))) + (should (equal (qrencode--find-version 231 'byte) '(10 . L))) + ;; Largest payload the format can carry, Table 7: 2953 at 40-L. + (should (equal (qrencode--find-version 2953 'byte) '(40 . L))) + (should-error (qrencode--find-version 2954 'byte) :type 'user-error) + ;; An explicit level takes the other branch of the function. + (should (equal (qrencode--find-version 100 'byte 'H) '(10 . H))) + (should (equal (qrencode--find-version 100 'byte 'L) '(5 . L))) + (should (equal (qrencode--find-version 1273 'byte 'H) '(40 . H))) + (should-error (qrencode--find-version 1274 'byte 'H) :type 'user-error)) ;; Analyse data diff --git a/qrencode.el b/qrencode.el index 7728c9d..51126c2 100644 --- a/qrencode.el +++ b/qrencode.el @@ -4,7 +4,7 @@ ;; Author: Rüdiger Sonderfeld ;; Keywords: qrcode comm -;; Version: 1.4 +;; Version: 1.5-beta1 ;; Package-Requires: ((emacs "25.1")) ;; Package: qrencode ;; URL: https://github.com/ruediger/qrencode-el @@ -652,7 +652,7 @@ Optionally provide a MASK or #x5412 is used." (defun qrencode--length-in-version (n version mode) "Return length of a string of size N in VERSION and MODE." - (+ n (ceiling (+ 4 (/ (qrencode--char-count-bits version mode) 8))))) + (+ n (ceiling (+ 4 (qrencode--char-count-bits version mode)) 8))) (defconst qrencode--size-table [(26 . ((L . ( 7 3 1)) From 3148262e2410f5cd747a45a66a5bd5488e2899a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 15:47:15 +0100 Subject: [PATCH 03/15] Fix: Remove spurious extra byte. This was a byte left over from initial development. Kinda slipped through all these years. It didn't cause a bad decoding but wasted a byte. --- qrencode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qrencode.el b/qrencode.el index 51126c2..c7328f4 100644 --- a/qrencode.el +++ b/qrencode.el @@ -892,8 +892,8 @@ QRCode is returned instead of a formatted string." (errcorrlen (cadr (assq errcorr (cdr size-table)))) (datalen (- qrlen errcorrlen)) (padding [#xEC #x11])) - (setq data (vconcat data [#x40] ; TODO: why the #x40? - (cl-loop for i from 0 below (- datalen (length data) 1) + (setq data (vconcat data + (cl-loop for i from 0 below (- datalen (length data)) vconcat (vector (aref padding (% i 2)))))) ;; Step 3: Error correction coding From 68115da8e1b68043431f3d5c2e79438adcf42f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 16:52:39 +0100 Subject: [PATCH 04/15] fix: Make test depend on qrencode.elc. That way qrencode.elc gets rebuild instead of accidentally testing a stale version. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1c8d2e6..958a0cb 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ TESTS := $(wildcard *-tests.el) all: doc qrencode.elc .PHONY: test -test: $(TESTS) +test: qrencode.elc $(TESTS) $(BATCH) -l ert $(foreach file,$^,-l $(file)) -f ert-run-tests-batch-and-exit README.html: README.org From 42573a15604ea2354ac12520cdfce63efed928f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:01:03 +0100 Subject: [PATCH 05/15] fix: Various fixes and clean ups for penalty calculation. * Split penalties into separate functions for each rule to simplify testing * Fix skipping last col / row (added test) * Fix dark-light ratio calculation (added test) * More tests --- qrencode-tests.el | 156 +++++++++++++++++++------- qrencode.el | 280 ++++++++++++++++++++++++---------------------- 2 files changed, 261 insertions(+), 175 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index 1382369..b51f907 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -180,53 +180,125 @@ ;; TODO: Test qrencode--draw-data ;;; Data masking -(ert-deftest qrencode-penalty-test () +(ert-deftest qrencode-penalty-adjacency-test () + "Test rule 1 penalties." ;; Exactly five same colour should not incur penalty - (should (< (qrencode--penalty [[1 1 1 1 1 0] - [0 1 0 1 0 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1]]) - 3)) + (should (= (qrencode--penalty-adjacency [[1 1 1 1 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1]]) + 0)) ;; Six of same colour should incurs penalty - (should (>= (qrencode--penalty [[1 1 1 1 1 1] - [0 1 0 1 0 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1]]) - 3)) + (should (= (qrencode--penalty-adjacency [[1 1 1 1 1 1] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1]]) + 4)) ;; Six of same colour should incurs penalty (different row) - (should (>= (qrencode--penalty [[0 1 0 1 0 1] - [1 1 1 1 1 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1] - [1 0 1 0 1 0] - [0 1 0 1 0 1]]) - 3)) + (should (= (qrencode--penalty-adjacency [[0 1 0 1 0 1] + [1 1 1 1 1 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1]]) + 4)) ;; Six of same colour should incurs penalty (cols) - (should (>= (qrencode--penalty [[0 1 0 1 0 1] - [1 1 0 1 1 0] - [1 1 1 0 1 0] - [0 1 0 1 0 1] - [1 1 1 0 1 0] - [0 1 0 1 0 1]]) - 3)) - ;; 1:1:3:1:1 penalty - (should (>= (qrencode--penalty [[1 0 1 0 1 0 0 1 0 1 0] - [0 0 0 0 1 0 1 1 1 0 1] - [1 0 1 0 1 0 1 0 1 0 1] - [0 1 0 1 0 1 0 1 0 1 0] - [1 0 1 0 1 0 1 0 1 0 1] - [0 1 0 1 0 1 0 1 0 1 0] - [1 0 1 0 1 0 1 0 1 0 1] - [0 1 0 1 0 1 0 1 0 1 0] - [1 0 1 0 1 0 1 0 1 0 1] - [0 1 0 1 0 1 0 1 0 1 0] - [1 0 1 0 1 0 1 0 1 0 1]]) - 40))) + (should (= (qrencode--penalty-adjacency [[0 1 0 1 0 1] + [1 1 0 1 1 0] + [1 1 1 0 1 0] + [0 1 0 1 0 1] + [1 1 1 0 1 0] + [0 1 0 1 0 1]]) + 4)) + ;; Six of same colour should incurs penalty (last row) + (should (= (qrencode--penalty-adjacency [[0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 1 1 1 1 1]]) + 4)) + ;; Six of same colour should incurs penalty (last cols) + (should (= (qrencode--penalty-adjacency [[1 0 1 0 1 1] + [0 1 0 1 0 1] + [1 0 1 0 1 1] + [0 1 0 1 0 1] + [1 0 1 0 1 1] + [0 1 0 1 0 1]]) + 4))) + +(ert-deftest qrencode-penalty-blocks-test () + "Test 2x2 block rule" + (should (= (qrencode--penalty-blocks [[0 1 0 1 0 1] + [1 1 0 1 1 0] + [1 1 1 0 1 0] + [0 1 0 1 0 1] + [1 1 1 0 1 0] + [0 1 0 1 0 1]]) + 3))) + +(ert-deftest qrencode-penalty-11311-test () + "Test 1:1:3:1:1 penalty" + (should (= (qrencode--penalty-11311 + [[1 0 1 0 1 0 0 1 0 1 0] + [0 0 0 0 1 0 1 1 1 0 1] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1]]) + 40))) + +(ert-deftest qrencode-penalty-dark-light-ratio-test () + "Test dark light ratio penalty." + (should (= (qrencode--penalty-dark-light-ratio + [[1 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 1]]) + 10))) + +(ert-deftest qrencode-no-penalty-test () + "Checker board should be zero penalty." + (should (= (qrencode--penalty [[0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0] + [0 1 0 1 0 1] + [1 0 1 0 1 0]]) + 0))) +(ert-deftest qrencode-penalty-test () + "`qrencode--penalty' must be the sum of all four rules." + (let ((qr [[1 0 1 0 1 0 0 1 0 1 0] + [0 0 0 0 1 0 1 1 1 0 1] + [1 0 1 0 1 0 1 0 1 0 1] + [1 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 1 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1] + [0 1 0 1 0 1 0 1 1 1 0] + [1 1 1 1 1 1 1 0 1 0 1] + [1 1 0 1 0 1 0 1 0 1 0] + [1 0 1 0 1 0 1 0 1 0 1]])) + ;; This board trips every rule, so a missing term cannot cancel out: + ;; 5 (a run of seven) + 3 (one 2x2 block) + 40 (one 1:1:3:1:1) + + ;; 10 (67/121 = 55.4% dark, one 5% step from half). + (should (= (qrencode--penalty-adjacency qr) 5)) + (should (= (qrencode--penalty-blocks qr) 3)) + (should (= (qrencode--penalty-11311 qr) 40)) + (should (= (qrencode--penalty-dark-light-ratio qr) 10)) + (should (= (qrencode--penalty qr) 58)))) (ert-deftest qrencode-masks-test () (should (equal (qrencode--apply-mask (qrencode--square 10) (qrencode--square 10) 0) diff --git a/qrencode.el b/qrencode.el index c7328f4..f694045 100644 --- a/qrencode.el +++ b/qrencode.el @@ -354,146 +354,160 @@ The square is initialised with INIT or 0." ;;; Data masking -(defun qrencode--penalty (qr) - "Return penalty (the higher the worse) for a given QR pattern." +(defun qrencode--penalty-adjacency (qr) + "Return penalty for rule 1: Adjacency. +More than 5 adjacent modules of same colour." + ;; 1. Adjacency: + ;; More than 5 adjacent modules of same colour. (let ((size (length qr)) - (penalty 0)) - - ;; 1. Adjacency: - ;; More than 5 adjacent modules of same colour. - (let ((N1 3)) - ;; Scan columns - (let ((row 0) (col 0)) - (while (< row (1- size)) - (while (< col (1- size)) - (if (= (qrencode--aaref qr col row) - (qrencode--aaref qr (1+ col) row)) - (let ((i 1)) - (while (and (< col (1- size)) (= (qrencode--aaref qr col row) - (qrencode--aaref qr (1+ col) row))) - (setq i (1+ i) - col (1+ col))) - (when (> i 5) - (setq penalty (+ penalty N1 (- i 5))))) - (setq col (1+ col)))) - (setq row (1+ row) - col 0))) - - ;; Scan rows - (let ((row 0) (col 0)) + (penalty 0) + (N1 3)) + ;; Scan columns + (let ((row 0) (col 0)) + (while (< row size) (while (< col (1- size)) - (while (< row (1- size)) - (if (= (qrencode--aaref qr col row) - (qrencode--aaref qr col (1+ row))) - (let ((i 1)) - (while (and (< row (1- size)) (= (qrencode--aaref qr col row) - (qrencode--aaref qr col (1+ row)))) - (setq i (1+ i) - row (1+ row))) - (when (> i 5) - (setq penalty (+ penalty N1 (- i 5))))) - (setq row (1+ row)))) - (setq col (1+ col) - row 0)))) - - ;; 2. Block (2×2 block) of modules of the same colour - (let ((N2 3)) - (setq penalty (+ penalty - (cl-loop for row from 0 below (1- size) - sum (cl-loop for col from 0 below (1- size) - when (= (qrencode--aaref qr col row) - (qrencode--aaref qr col (1+ row)) - (qrencode--aaref qr (1+ col) row) - (qrencode--aaref qr (1+ col) (1+ row))) - sum N2))))) - - ;; 3. 1:1:3:1:1 pattern - ;; Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000. - (let ((N3 40)) - (let ((row 0) (col 0)) + (if (= (qrencode--aaref qr col row) + (qrencode--aaref qr (1+ col) row)) + (let ((i 1)) + (while (and (< col (1- size)) (= (qrencode--aaref qr col row) + (qrencode--aaref qr (1+ col) row))) + (setq i (1+ i) + col (1+ col))) + (when (> i 5) + (setq penalty (+ penalty N1 (- i 5))))) + (setq col (1+ col)))) + (setq row (1+ row) + col 0))) + ;; Scan rows + (let ((row 0) (col 0)) + (while (< col size) (while (< row (1- size)) - (while (< col (- size 10)) - ;; Optimisation: Both patterns match in these spots - (when (and (= 0 (qrencode--aaref qr (+ col 1) row)) - (= 1 (qrencode--aaref qr (+ col 4) row)) - (= 0 (qrencode--aaref qr (+ col 5) row)) - (= 1 (qrencode--aaref qr (+ col 6) row)) - (= 0 (qrencode--aaref qr (+ col 9) row)) - (or (and - ;; Pattern beginning with 0 - (= 0 (qrencode--aaref qr col row)) - ;; 0 - (= 0 (qrencode--aaref qr (+ col 2) row)) - (= 0 (qrencode--aaref qr (+ col 3) row)) - ;; 1 - (= 1 (qrencode--aaref qr (+ col 7) row)) - (= 1 (qrencode--aaref qr (+ col 8) row)) - ;; 0 - (= 1 (qrencode--aaref qr (+ col 10) row))) - (and - ;; Pattern ending with 0 - (= 1 (qrencode--aaref qr col row)) - ;; 0 - (= 1 (qrencode--aaref qr (+ col 2) row)) - (= 1 (qrencode--aaref qr (+ col 3) row)) - ;; 1 - (= 0 (qrencode--aaref qr (+ col 7) row)) - (= 0 (qrencode--aaref qr (+ col 8) row)) - ;; 0 - (= 0(qrencode--aaref qr (+ col 10) row))))) - (setq penalty (+ penalty N3))) - (setq col (1+ col))) - (setq row (1+ row) - col 0))) - - (let ((row 0) (col 0)) - (while (< col (1- size)) - (while (< row (- size 10)) - ;; Optimisation: Both patterns match in these spots - (when (and (= 0 (qrencode--aaref qr col (+ row 1))) - (= 1 (qrencode--aaref qr col (+ row 4))) - (= 0 (qrencode--aaref qr col (+ row 5))) - (= 1 (qrencode--aaref qr col (+ row 6))) - (= 0 (qrencode--aaref qr col (+ row 9))) - (or (and - ;; Pattern beginning with 0 - (= 0 (qrencode--aaref qr col row)) - ;; 0 - (= 0 (qrencode--aaref qr col (+ row 2))) - (= 0 (qrencode--aaref qr col (+ row 3))) - ;; 1 - (= 1 (qrencode--aaref qr col (+ row 7))) - (= 1 (qrencode--aaref qr col (+ row 8))) - ;; 0 - (= 1 (qrencode--aaref qr col (+ row 10)))) - (and - ;; Pattern ending with 0 - (= 1 (qrencode--aaref qr col row)) - ;; 0 - (= 1 (qrencode--aaref qr col (+ row 2))) - (= 1 (qrencode--aaref qr col (+ row 3))) - ;; 1 - (= 0 (qrencode--aaref qr col (+ row 7))) - (= 0 (qrencode--aaref qr col (+ row 8))) - ;; 0 - (= 0(qrencode--aaref qr col (+ row 10)))))) - (setq penalty (+ penalty N3))) - (setq row (1+ row))) - (setq col (1+ col) - row 0)))) - - ;; 4. Ratio of dark to light - (let ((N4 10) - (dark (cl-loop for row across qr - sum (cl-loop for d across row sum d)))) - (setq penalty - (+ penalty - ;; Every 5% deviation from 50% dark/white ratio is penalised. - (* (floor (/ (abs (- 0.5 (/ dark (* size size)))) 0.05) N4))))) + (if (= (qrencode--aaref qr col row) + (qrencode--aaref qr col (1+ row))) + (let ((i 1)) + (while (and (< row (1- size)) (= (qrencode--aaref qr col row) + (qrencode--aaref qr col (1+ row)))) + (setq i (1+ i) + row (1+ row))) + (when (> i 5) + (setq penalty (+ penalty N1 (- i 5))))) + (setq row (1+ row)))) + (setq col (1+ col) + row 0))) + penalty)) +(defun qrencode--penalty-blocks (qr) + "Return penalty for rule 2: Block (2×2 block) of modules of the same colour." + (let ((size (length qr)) + (N2 3)) + (cl-loop for row from 0 below (1- size) + sum (cl-loop for col from 0 below (1- size) + when (= (qrencode--aaref qr col row) + (qrencode--aaref qr col (1+ row)) + (qrencode--aaref qr (1+ col) row) + (qrencode--aaref qr (1+ col) (1+ row))) + sum N2)))) + +(defun qrencode--penalty-11311 (qr) + "Return penalty for rule 3: 1:1:3:1:1 pattern. +Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000." + (let ((size (length qr)) + (N3 40) + (penalty 0)) + (let ((row 0) (col 0)) + (while (< row size) + (while (< col (- size 10)) + ;; Optimisation: Both patterns match in these spots + (when (and (= 0 (qrencode--aaref qr (+ col 1) row)) + (= 1 (qrencode--aaref qr (+ col 4) row)) + (= 0 (qrencode--aaref qr (+ col 5) row)) + (= 1 (qrencode--aaref qr (+ col 6) row)) + (= 0 (qrencode--aaref qr (+ col 9) row)) + (or (and + ;; Pattern beginning with 0 + (= 0 (qrencode--aaref qr col row)) + ;; 0 + (= 0 (qrencode--aaref qr (+ col 2) row)) + (= 0 (qrencode--aaref qr (+ col 3) row)) + ;; 1 + (= 1 (qrencode--aaref qr (+ col 7) row)) + (= 1 (qrencode--aaref qr (+ col 8) row)) + ;; 0 + (= 1 (qrencode--aaref qr (+ col 10) row))) + (and + ;; Pattern ending with 0 + (= 1 (qrencode--aaref qr col row)) + ;; 0 + (= 1 (qrencode--aaref qr (+ col 2) row)) + (= 1 (qrencode--aaref qr (+ col 3) row)) + ;; 1 + (= 0 (qrencode--aaref qr (+ col 7) row)) + (= 0 (qrencode--aaref qr (+ col 8) row)) + ;; 0 + (= 0(qrencode--aaref qr (+ col 10) row))))) + (setq penalty (+ penalty N3))) + (setq col (1+ col))) + (setq row (1+ row) + col 0))) + + (let ((row 0) (col 0)) + (while (< col size) + (while (< row (- size 10)) + ;; Optimisation: Both patterns match in these spots + (when (and (= 0 (qrencode--aaref qr col (+ row 1))) + (= 1 (qrencode--aaref qr col (+ row 4))) + (= 0 (qrencode--aaref qr col (+ row 5))) + (= 1 (qrencode--aaref qr col (+ row 6))) + (= 0 (qrencode--aaref qr col (+ row 9))) + (or (and + ;; Pattern beginning with 0 + (= 0 (qrencode--aaref qr col row)) + ;; 0 + (= 0 (qrencode--aaref qr col (+ row 2))) + (= 0 (qrencode--aaref qr col (+ row 3))) + ;; 1 + (= 1 (qrencode--aaref qr col (+ row 7))) + (= 1 (qrencode--aaref qr col (+ row 8))) + ;; 0 + (= 1 (qrencode--aaref qr col (+ row 10)))) + (and + ;; Pattern ending with 0 + (= 1 (qrencode--aaref qr col row)) + ;; 0 + (= 1 (qrencode--aaref qr col (+ row 2))) + (= 1 (qrencode--aaref qr col (+ row 3))) + ;; 1 + (= 0 (qrencode--aaref qr col (+ row 7))) + (= 0 (qrencode--aaref qr col (+ row 8))) + ;; 0 + (= 0(qrencode--aaref qr col (+ row 10)))))) + (setq penalty (+ penalty N3))) + (setq row (1+ row))) + (setq col (1+ col) + row 0))) penalty)) +(defun qrencode--penalty-dark-light-ratio (qr) + "Return penalty for rule 4: Ratio of dark to light." + (let ((N4 10) + (size (length qr)) + (dark (cl-loop for row across qr + sum (cl-loop for d across row sum d)))) + ;; Every 5% deviation from 50% dark/light ratio is penalised. + ;; (* (floor (/ (abs (- (/ (float dark) (* size size)) 0.5)) 0.05)) N4) + ;; Instead of using float we use integer arithmetic though to avoid + ;; rounding cases. + (* N4 (/ (abs (- (* 20 dark) (* 10 size size))) (* size size))))) + +(defun qrencode--penalty (qr) + "Return penalty (the higher the worse) for a given QR pattern." + (+ + (qrencode--penalty-adjacency qr) ;; Rule 1 + (qrencode--penalty-blocks qr) ;; Rule 2 + (qrencode--penalty-11311 qr) ;; Rule 3 + (qrencode--penalty-dark-light-ratio qr))) ;; Rule 4 + (defconst qrencode--masks [(lambda (i j) (= (% (+ i j) 2) 0)) (lambda (i _j) (= (% i 2) 0)) From 98498897ae74e19f64485793803e9f6dbc44a360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:08:59 +0100 Subject: [PATCH 06/15] fix: minor transposed column/row bug. This didn't impact anything due to symmetry but better not have hidden issues. --- qrencode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qrencode.el b/qrencode.el index f694045..e2ef972 100644 --- a/qrencode.el +++ b/qrencode.el @@ -350,7 +350,7 @@ The square is initialised with INIT or 0." (setq row nrow column ncolumn up nup)) - while (= (qrencode--aaref function-pattern row column) 1))))))) + while (= (qrencode--aaref function-pattern column row) 1))))))) ;;; Data masking From 414cf3981c2317b55912c26b86b7b33696700939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:11:09 +0100 Subject: [PATCH 07/15] fix: malformed :package-version. --- qrencode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qrencode.el b/qrencode.el index e2ef972..6fd530a 100644 --- a/qrencode.el +++ b/qrencode.el @@ -1032,7 +1032,7 @@ FILENAME of the exported file is passed as parameter. For example this can be used to convert the output to a different bitmap format." :type 'hook - :package-version "1.2-beta1" + :package-version '(qrencode . "1.2-beta1") :group 'qrencode) (defface qrencode-face From c73ed75d4ad2cd6e0eba6b852311d28018b7bd21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:19:46 +0100 Subject: [PATCH 08/15] fix: Fix docstrings and passing mode. Only `byte` mode is supported. But still better to pass it all the way through. --- qrencode.el | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/qrencode.el b/qrencode.el index 6fd530a..be882a4 100644 --- a/qrencode.el +++ b/qrencode.el @@ -48,7 +48,7 @@ ;; Reed solomon ECC implementation based on https://research.swtch.com/field (defun qrencode--mul-no-lut (x y poly) - "Caryless-multiply X and Y modulo POLY." + "Carryless-multiply X and Y modulo POLY." (let ((z 0)) (while (> x 0) (when (/= (logand x 1) 0) @@ -150,16 +150,16 @@ Optionally provide a FIELD and LGEN (log of generator polynomial)." ;; TODO(#11): Support other encodings (other (error "Mode %s not supported" other)))) -(defun qrencode--encode-byte (input version) +(defun qrencode--encode-byte (input version &optional mode) "Return INPUT encoded in byte format for QR Code size VERSION. -See Section 7.4 of ISO/IEC standard. This code adds a 4 bit mode +See Section 7.4 of ISO/IEC standard. This code adds a 4 bit MODE indicator and then the character count in either 16 bit (for version > 9) or 8 bit, followed by input." (let* ((l (length input)) (rest (logand l #xF))) (cl-assert (<= l (if (> version 9) 65535 255))) (vconcat - (vector (logior (ash (qrencode--mode 'byte) 4) + (vector (logior (ash (qrencode--mode (or mode 'byte)) 4) ;; Version <=9 use 8 bit, larger 16 bit for size (ash l (if (<= version 9) -4 -12)))) (when (> version 9) @@ -356,7 +356,7 @@ The square is initialised with INIT or 0." ;;; Data masking (defun qrencode--penalty-adjacency (qr) - "Return penalty for rule 1: Adjacency. + "Return penalty of QR code for rule 1: Adjacency. More than 5 adjacent modules of same colour." ;; 1. Adjacency: ;; More than 5 adjacent modules of same colour. @@ -398,7 +398,8 @@ More than 5 adjacent modules of same colour." penalty)) (defun qrencode--penalty-blocks (qr) - "Return penalty for rule 2: Block (2×2 block) of modules of the same colour." + "Return penalty for QR code for rule 2. +Penalty for block (2×2 block) of modules of the same colour." (let ((size (length qr)) (N2 3)) (cl-loop for row from 0 below (1- size) @@ -410,7 +411,7 @@ More than 5 adjacent modules of same colour." sum N2)))) (defun qrencode--penalty-11311 (qr) - "Return penalty for rule 3: 1:1:3:1:1 pattern. + "Return penalty of QR code for rule 3: 1:1:3:1:1 pattern. Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000." (let ((size (length qr)) (N3 40) @@ -489,7 +490,7 @@ Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000. penalty)) (defun qrencode--penalty-dark-light-ratio (qr) - "Return penalty for rule 4: Ratio of dark to light." + "Return penalty of QR code for rule 4: Ratio of dark to light." (let ((N4 10) (size (length qr)) (dark (cl-loop for row across qr @@ -899,7 +900,7 @@ QRCode is returned instead of a formatted string." errcorr ec)) ;; Step 2: Encode data - (setq data (qrencode--encode-byte raw-bytes version)) + (setq data (qrencode--encode-byte raw-bytes version mode)) ;; Add padding (let* ((size-table (aref qrencode--size-table (1- version))) (qrlen (car size-table)) From fc585015a76c8941062c1df29a0104eb1d9517a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:22:20 +0100 Subject: [PATCH 09/15] fix: Fix qrencode--masks to byte compile. Using `vector` instead of a literal means the byte compiler will compile the lambdas. Speeding up execution of the code. --- qrencode.el | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qrencode.el b/qrencode.el index be882a4..c1a2d35 100644 --- a/qrencode.el +++ b/qrencode.el @@ -510,14 +510,14 @@ Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000. (qrencode--penalty-dark-light-ratio qr))) ;; Rule 4 (defconst qrencode--masks - [(lambda (i j) (= (% (+ i j) 2) 0)) - (lambda (i _j) (= (% i 2) 0)) - (lambda (_i j) (= (% j 3) 0)) - (lambda (i j) (= (% (+ i j) 3) 0)) - (lambda (i j) (= (% (+ (/ i 2) (/ j 3)) 2) 0)) - (lambda (i j) (= (+ (% (* i j) 2) (% (* i j) 3)) 0)) - (lambda (i j) (= (% (+ (% (* i j) 2) (% (* i j) 3)) 2) 0)) - (lambda (i j) (= (% (+ (% (+ i j) 2) (% (* i j) 3)) 2) 0))] + (vector (lambda (i j) (= (% (+ i j) 2) 0)) + (lambda (i _j) (= (% i 2) 0)) + (lambda (_i j) (= (% j 3) 0)) + (lambda (i j) (= (% (+ i j) 3) 0)) + (lambda (i j) (= (% (+ (/ i 2) (/ j 3)) 2) 0)) + (lambda (i j) (= (+ (% (* i j) 2) (% (* i j) 3)) 0)) + (lambda (i j) (= (% (+ (% (* i j) 2) (% (* i j) 3)) 2) 0)) + (lambda (i j) (= (% (+ (% (+ i j) 2) (% (* i j) 3)) 2) 0))) "Mask patterns.") (defun qrencode--copy (seq) From 14d0f67daa94c68d5c83527f7439007ff00e0cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:31:56 +0100 Subject: [PATCH 10/15] fix: Minor code cleanups including a one-off error. --- qrencode.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qrencode.el b/qrencode.el index c1a2d35..fd0f3e9 100644 --- a/qrencode.el +++ b/qrencode.el @@ -297,7 +297,7 @@ The square is initialised with INIT or 0." ;; Timing pattern - (cl-loop for i from 8 to (- size 8) + (cl-loop for i from 8 to (- size 9) do (qrencode--aaset qrcode 6 i (% (1+ i) 2)) do (qrencode--aaset function-pattern 6 i 1) do (qrencode--aaset qrcode i 6 (% (1+ i) 2)) @@ -1110,8 +1110,8 @@ Commands: When called interactively, read STR with `read-string'." (interactive "sString: ") (let ((trimmed-str (string-trim str))) - (if (string-empty-p trimmed-str) - (user-error "Empty string")) + (when (string-empty-p trimmed-str) + (user-error "Empty string")) (qrencode--encode-to-buffer trimmed-str))) (provide 'qrencode) From 15caed42a78a844df3df1fa3d1543f12dbedd3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 1 Aug 2026 23:43:09 +0100 Subject: [PATCH 11/15] fix: Use skip-unless in zbarimg test. This makes it explicit that the test was skipped. --- qrencode-tests.el | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index b51f907..a1c3de3 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -529,27 +529,25 @@ copy along the right and bottom edges." "))) (ert-deftest qrencode-zbarimg-test () - "Test decoding generated QRCodes using the zbarimg program. -Note: This test will succeed silently if zbarimg is not found." + "Test decoding generated QRCodes using the zbarimg program." (let ((zbarimg (executable-find "zbarimg"))) - (if (null zbarimg) - (message "zbarimg not found. Not running all tests!") - (let ((tmpfile (make-temp-file "qr" nil ".pbm"))) - (cl-loop for input across - ["hello" - "https://github.com/ruediger/qrencode-el" - "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello" - "qrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqr" - - ;; escaped Unicode characters - "\U0001f600\U0001f680\u3042" - ;; raw UTF-8 characters - "😸🚗愛" - ] - do (with-temp-file tmpfile - (insert (qrencode-format-as-netpbm (qrencode input nil nil 'return-raw)))) - do (should (string= (shell-command-to-string (format "%s -q '%s'" zbarimg tmpfile)) - (format "QR-Code:%s\n" input)))))))) + (skip-unless zbarimg) + (let ((tmpfile (make-temp-file "qr" nil ".pbm"))) + (cl-loop for input across + ["hello" + "https://github.com/ruediger/qrencode-el" + "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello" + "qrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqrqr" + + ;; escaped Unicode characters + "\U0001f600\U0001f680\u3042" + ;; raw UTF-8 characters + "😸🚗愛" + ] + do (with-temp-file tmpfile + (insert (qrencode-format-as-netpbm (qrencode input nil nil 'return-raw)))) + do (should (string= (shell-command-to-string (format "%s -q '%s'" zbarimg tmpfile)) + (format "QR-Code:%s\n" input))))))) (provide 'qrencode-tests) ;;; qrencode-tests.el ends here From ee18f63380b2be5d9b7d7670f0d229bec1a15bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 2 Aug 2026 00:04:32 +0100 Subject: [PATCH 12/15] fix: Include version/format information when picking mask. The standard specifies a step order but actually step 7 and 6 seem to be out of order. When calculating penalties and picking the right mask (step 6) the version/format information (step 7) should be included. This is now fixed. --- qrencode.el | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/qrencode.el b/qrencode.el index fd0f3e9..70486f6 100644 --- a/qrencode.el +++ b/qrencode.el @@ -540,16 +540,18 @@ Pattern: 4 light modules before/after 1011101. I.e., 00001011101 or 10111010000. (if (funcall m i j) 1 0))))) qr)) -(defun qrencode--find-best-mask (qr function-pattern) - "Return QR with best mask applied and mask number, avoiding FUNCTION-PATTERN." +(defun qrencode--find-best-mask (qr function-pattern errcorr) + "Return QR with best mask applied and mask number, avoiding FUNCTION-PATTERN. +This encodes ERRCORR and mask information." (let (bestqr (bestmask 0) (bestpenalty #xFFFFFFFF)) (dotimes (mask (length qrencode--masks)) - (let* ((newqr (qrencode--apply-mask qr function-pattern mask)) - (penalty (qrencode--penalty newqr))) - (when (< penalty bestpenalty) - (setq bestqr newqr - bestmask mask - bestpenalty penalty)))) + (let ((newqr (qrencode--apply-mask qr function-pattern mask))) + (qrencode--encode-info newqr errcorr mask) + (let ((penalty (qrencode--penalty newqr))) + (when (< penalty bestpenalty) + (setq bestqr newqr + bestmask mask + bestpenalty penalty))))) (cons bestqr bestmask))) ;;; Version/Info encoding @@ -888,7 +890,7 @@ is `byte'. If RETURN-RAW is set a raw vector version of the QRCode is returned instead of a formatted string." ;; Following Section 7.1 from ISO/IEC 18004 2015 - (let (version raw-bytes data qr function-pattern datamask) + (let (version raw-bytes data qr function-pattern) ;; Step 1: Analyse data ;; TODO(#11): find suitable mode. For now we only support byte (setq mode (or mode 'byte)) @@ -936,15 +938,16 @@ QRCode is returned instead of a formatted string." (setq qr qrcode function-pattern fp)) - ;; Step 6: Data masking - (pcase-let ((`(,qrcodemasked . ,mask) (qrencode--find-best-mask qr function-pattern))) - (setq qr qrcodemasked - datamask mask)) - - ;; Step 7: Format and version information - (qrencode--encode-info qr errcorr datamask) + ;; Step 7: Format and version information. + ;; The steps are out of order here since the masking gets impacted + ;; by the format and version information. We encode version here + ;; the format information is encoded as part of find-best-mask. (qrencode--encode-version qr version) + ;; Step 6: Data masking + (pcase-let ((`(,qrcodemasked . ,_mask) (qrencode--find-best-mask qr function-pattern errcorr))) + (setq qr qrcodemasked)) + (if return-raw qr (qrencode-format qr)))) From 95c92d714b2ba0a4c2fa88a398320aeaf6611665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 2 Aug 2026 00:41:04 +0100 Subject: [PATCH 13/15] fix: More minor code cleanup. Use cond instead of pcase which means we don't need the `qrencode--unused` hack. Also enforce a valid mode is set. --- qrencode.el | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/qrencode.el b/qrencode.el index 70486f6..9d9661a 100644 --- a/qrencode.el +++ b/qrencode.el @@ -639,33 +639,29 @@ Optionally provide a MASK or #x5412 is used." (qrencode--aaset qr (+ c1 a) (+ r1 b) val) (qrencode--aaset qr (+ c2 b) (+ r2 a) val))))))) -(defun qrencode--unused (_) - "This doesn't use _ but tricks the compiler.") - ;; Analyse data: sizing etc. (defun qrencode--char-count-bits (version mode) "Return the number of bits per character given VERSION and MODE." - (cdr (assq mode - (pcase version - ((and n (guard (<= 1 n 9))) - (qrencode--unused n) + (or + (cdr (assq mode + (cond + ((<= 1 version 9) '((numeric . 10) (alphanumeric . 9) (byte . 8) (kanji . 8))) - ((and n (guard (<= 10 n 26))) - (qrencode--unused n) + ((<= 10 version 26) '((numeric . 12) (alphanumeric . 11) (byte . 16) (kanji . 10))) - ((and n (guard (<= 27 n 40))) - (qrencode--unused n) + ((<= 27 version 40) '((numeric . 14) (alphanumeric . 13) (byte . 16) (kanji . 12))) - (other (error "Unsupported version %d (range 1 to 40)" other)))))) + (t (error "Unsupported version %d (range 1 to 40)" version))))) + (error "Unknown mode %s" mode))) (defun qrencode--length-in-version (n version mode) "Return length of a string of size N in VERSION and MODE." @@ -894,6 +890,8 @@ QRCode is returned instead of a formatted string." ;; Step 1: Analyse data ;; TODO(#11): find suitable mode. For now we only support byte (setq mode (or mode 'byte)) + ;; Signals in case of unsupported mode + (qrencode--mode mode) ;; Convert Emacs internal encoded characters into raw UTF-8 bytes (setq raw-bytes (encode-coding-string s 'utf-8)) ;; Find the version with the highest error correction to fit the data From d9c5da1ef1f57cf92f45a1d11fd992660681dfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 2 Aug 2026 01:03:52 +0100 Subject: [PATCH 14/15] fix: More minor code cleanup. Remove unnecessary save-excursion and move pop-to-buffer out of with-current-buffer. --- qrencode.el | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/qrencode.el b/qrencode.el index 9d9661a..f9aed25 100644 --- a/qrencode.el +++ b/qrencode.el @@ -1076,18 +1076,17 @@ Commands: (defun qrencode--encode-to-buffer (s) "Encode S as QR Code and insert into `qrencode-buffer-name`." - (save-excursion - (let ((buf (get-buffer-create qrencode-buffer-name))) - (with-current-buffer buf - (let ((inhibit-read-only t)) - (erase-buffer) - (qrencode-mode) - (setq-local line-spacing nil) ; ensure no line spacing - (setq-local qrencode--raw-qr (qrencode s nil nil 'return-raw)) - (insert (propertize (qrencode-format qrencode--raw-qr) 'face 'qrencode-face)) - (insert "\nEncoded Text:\n" s) - (goto-char (point-min))) - (pop-to-buffer buf))))) + (let ((buf (get-buffer-create qrencode-buffer-name))) + (with-current-buffer buf + (let ((inhibit-read-only t)) + (erase-buffer) + (qrencode-mode) + (setq-local line-spacing nil) ; ensure no line spacing + (setq-local qrencode--raw-qr (qrencode s nil nil 'return-raw)) + (insert (propertize (qrencode-format qrencode--raw-qr) 'face 'qrencode-face)) + (insert "\nEncoded Text:\n" s) + (goto-char (point-min)))) + (pop-to-buffer buf))) ;;;###autoload From dc796820437fde3b3606af7a0af921207c5eeb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 2 Aug 2026 01:08:09 +0100 Subject: [PATCH 15/15] Update qr-self.png. The previous version was generated with a very early version of the code that didn't properly implement penalties. This version should be better up to standard. --- qr-self.png | Bin 495 -> 381 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/qr-self.png b/qr-self.png index 157f9f0cf81d21255e8177a9b4b15808090a878b..731485d572efe91c7469335e2e53f91e072e4f0e 100644 GIT binary patch delta 337 zcmV-X0j~b<1N{P!B#|@}f9ePV00$2~jG9;g00AROL_t(I%hl99j)fo)0N^ncbe@2O zYtV5exq$dCpjV>f8YEr-I|{<=X!6!=p2K###KO;H66R09pQpk9U<$-lF zI?L*@gF(#_d{L9^;$+ikYbQObq4W`}FW`#$zT^YXe50c0PX9CMe`NY7@4`Ot2x*jR z0yP`WGFuAVDUks?2#KPOD7JtsG3poYTTJeXeP#n{kZx8ti7bzT%_cC_2hB5_$$IKk z4^b~xV1c0^%Vw#eh(Tx5L$OG)MO=X=^@z%{N;O2Dh~gD-v9*Vd76HX#0VL0Bu7GAc z-$VG3=es#aWirT;M|Q!5a|Ut`X@5-}Exglb=n4Sri`89>XIr<}L9Dacsh;cmk`KK0 jH@QG_%kz)?pMSwGkvJ+34)prJ00000NkvXXu0mjf8!MgI delta 450 zcmey%^qzTw3NK5#qpu?a!^VE@KZ&dp6~*hHaXnVX zM-&8DFKm>VaFbWzBAcY{Otx$8%+bP8_Zt=<;Z}==9ux^|AUQ+mf zjQU@Gu|;QJ`j#pwZz{U4as67kT*8%ove`%8I^Ve*b4R04Oa8aO&i9n<)8bTPV$uQR+@vHv7>5HIYXII=wE z%ERdR$Y(2!+-F@{9m;ZR-;U(m6TxbafBO0N9%j(Koc81Szp09d-;e(afPxpW4_dhUS-Jy_mt6w*gfq_A_ z#5JNMC9x#cD!C{XNHG{07#ZpsSn3*>g%}xI85&s`8)zFCTNxNkZvHs~MMG|WN@iN6 X7H%Dn?m4Oebuf6k`njxgN@xNAm&(1X