From 32df2654fe3436e3284fced52ca25f5304aed577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sat, 26 Sep 2026 23:44:10 +0100 Subject: [PATCH 1/6] Add SVG support. SVG support requires svg.el, which is shipped with Emacs 26.1 and newer. This allows setting `qrencode-export-format` to `'svg` if available to write files in SVG format. A new API function `qrencode-as-svg` can be used to get an svg.el object if there is a desire to manipulate it further. --- qrencode.el | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/qrencode.el b/qrencode.el index e46a01b..224a043 100644 --- a/qrencode.el +++ b/qrencode.el @@ -4,7 +4,7 @@ ;; Author: Rüdiger Sonderfeld ;; Keywords: qrcode comm -;; Version: 1.5 +;; Version: 1.6-beta1 ;; Package-Requires: ((emacs "25.1")) ;; Package: qrencode ;; URL: https://github.com/ruediger/qrencode-el @@ -44,6 +44,9 @@ (require 'seq) (require 'thingatpt) +;; svg.el is only available in Emacs 26.1 and newer. +(require 'svg nil 'noerror) + ;;; Error correction ;; Reed solomon ECC implementation based on https://research.swtch.com/field @@ -1043,6 +1046,64 @@ Optionally specify PIXEL-SIZE (default is 3)." (dotimes (_ quiet-zone-size) (dotimes (_ bsize) (insert 0)))))) +(defun qrencode--svg-path (qr quiet-zone-size) + "Return svg path for QR with QUIET-ZONE-SIZE." + (with-temp-buffer + (let ((size (length qr))) + (dotimes (r size) + (let ((start 0) + (prev 0)) + (dotimes (c size) + ;; We only bother scanning one row at a time and turning + ;; series of 1's into a rectangle. + (let ((current (qrencode--aaref qr c r)) + (do-draw nil)) + (if (and (/= current prev) (= prev 1)) + (setq do-draw t) ;; draw if we change to 0 + (when (and (= c (1- size)) (= current 1)) ;; draw last pixel + (setq do-draw t + c (1+ c)) + (when (/= prev 1) + (setq start (1- c))))) + (when do-draw + (let ((block-len (- c start))) + (insert + ;; `Mx y` move to x,y absolute + ;; `hx` draw x horizontal relative + ;; `v1` draw 1 vertical relative + ;; `z` close + (format "M%d %dh%dv1h-%dz" + (+ start quiet-zone-size) + (+ r quiet-zone-size) + block-len + block-len)))) + (when (or do-draw (/= current prev)) + (setq prev current + start c))))))) + (buffer-string))) + +(defun qrencode-as-svg (qr &optional pixel-size inverse) + "Return an svg object of QR code. +See documentation of svg.el for how to use the object. +Optional argument PIXEL-SIZE (default is 3) and INVERSE to flip white/dark mode." + (let* ((size (length qr)) + (quiet-zone-size 4) + (pixel-size (or pixel-size 3)) + (size-wqz (+ quiet-zone-size size quiet-zone-size)) + (width-height (* size-wqz pixel-size)) + (background-colour (if inverse "#000" "#fff")) + (fill-colour (if inverse "#fff" "#000")) + (svgimg (svg-create width-height width-height :viewBox (format "0 0 %d %d" size-wqz size-wqz)))) + (svg-rectangle svgimg 0 0 size-wqz size-wqz :fill background-colour) ;; background + (svg-node svgimg 'path :d (qrencode--svg-path qr quiet-zone-size) :fill fill-colour) + svgimg)) + +(defun qrencode--write-as-svg (filename qr &optional pixel-size) + "Write QR as svg to FILENAME." + (with-temp-file filename + (svg-print (qrencode-as-svg qr pixel-size)) + (insert "\n"))) + (defgroup qrencode nil "QREncode: Encoder for QR Codes." :link '(url-link "https://github.com/ruediger/qrencode-el") @@ -1073,9 +1134,10 @@ bitmap format." P1 is the current default. But P4 export is much faster and produces smaller (binary) files. In the next major release the default will change to P4 and P1 support will be removed." - :type '(choice + :type `(choice (const :tag "NetPBM text (P1)" p1) - (const :tag "NetPBM binary (P4)" p4)) + (const :tag "NetPBM binary (P4)" p4) + ,@(when (featurep 'svg) '((const :tag "SVG" svg)))) :package-version '(qrencode . "1.5-beta4") :group 'qrencode) @@ -1094,6 +1156,8 @@ change to P4 and P1 support will be removed." (error "No raw QRCode data found") (let ((qr qrencode--raw-qr)) ; save ref to buffer local var. (pcase qrencode-export-format + ((and 'svg (guard (featurep 'svg))) + (qrencode--write-as-svg filename qr qrencode-export-pixel-size)) ('p4 (qrencode--write-as-netpbm-p4 filename qr qrencode-export-pixel-size)) ((or 'p1 'nil) From 10887177485e108f869bc2eea71bcfcfce66e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 27 Sep 2026 00:10:54 +0100 Subject: [PATCH 2/6] Add tests for SVG export. --- qrencode-tests.el | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index 973a973..a416eb9 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -553,7 +553,8 @@ The expected word for version 7 is the literal from section 7.10 of the (should (not (null qrencode--raw-qr))) (let ((tmpfile-p1 (make-temp-file "qr-p1" nil ".pbm")) (tmpfile-p4 (make-temp-file "qr-p4" nil ".pbm")) - p1 p4) + (tmpfile-svg (make-temp-file "qr-svg" nil ".svg")) + p1 p4 svg) (unwind-protect (progn (let ((qrencode-export-format 'p1)) @@ -564,18 +565,26 @@ The expected word for version 7 is the literal from section 7.10 of the (qrencode-export-buffer-to-file tmpfile-p4)) (setq p4 (qrencode-tests--get-file tmpfile-p4)) (should (string-prefix-p "P4\n" p4)) + (when (featurep 'svg) + (let ((qrencode-export-format 'svg)) + (qrencode-export-buffer-to-file tmpfile-svg)) + (setq svg (qrencode-tests--get-file tmpfile-svg)) + (should (string-prefix-p " Date: Sun, 27 Sep 2026 00:18:18 +0100 Subject: [PATCH 3/6] Fix Emacs <26.1 by declaring SVG functions. In case svg.el isn't available declare the SVG functions so byte compile won't fail due to them missing. --- qrencode.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qrencode.el b/qrencode.el index 224a043..663146e 100644 --- a/qrencode.el +++ b/qrencode.el @@ -1046,6 +1046,12 @@ Optionally specify PIXEL-SIZE (default is 3)." (dotimes (_ quiet-zone-size) (dotimes (_ bsize) (insert 0)))))) +;; Declare SVG functions in case svg.el is not available. +(declare-function svg-create "svg" (width height &rest args)) +(declare-function svg-rectangle "svg" (svg x y width height &rest args)) +(declare-function svg-node "svg" (svg tag &rest args)) +(declare-function svg-print "svg" (dom)) + (defun qrencode--svg-path (qr quiet-zone-size) "Return svg path for QR with QUIET-ZONE-SIZE." (with-temp-buffer From 52c70c9830f67a3c12ea539a8f5f459a0e3e77ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 27 Sep 2026 00:27:46 +0100 Subject: [PATCH 4/6] Add a test for svg-path. --- qrencode-tests.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qrencode-tests.el b/qrencode-tests.el index a416eb9..3715662 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -536,6 +536,12 @@ The expected word for version 7 is the literal from section 7.10 of the 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "))) +(ert-deftest qrencode-svg-path-test () + "Test SVG path." + (skip-unless (featurep 'svg)) + (should (string= (qrencode--svg-path [[0 0] [0 0]] 0) "")) + (should (string= (qrencode--svg-path [[1 1] [1 1]] 0) "M0 0h2v1h-2zM0 1h2v1h-2z"))) + (defun qrencode-tests--get-file (filename) "Helper function returning contents of FILENAME." (with-temp-buffer From 97f278f6ed0b676a71b424c13b3cbf4063e64f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 27 Sep 2026 00:39:24 +0100 Subject: [PATCH 5/6] Refactoring of qrencode--svg-path. Much cleaner code scanning ahead. --- qrencode-tests.el | 3 ++- qrencode.el | 49 +++++++++++++++++++---------------------------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/qrencode-tests.el b/qrencode-tests.el index 3715662..a26aa5c 100644 --- a/qrencode-tests.el +++ b/qrencode-tests.el @@ -540,7 +540,8 @@ The expected word for version 7 is the literal from section 7.10 of the "Test SVG path." (skip-unless (featurep 'svg)) (should (string= (qrencode--svg-path [[0 0] [0 0]] 0) "")) - (should (string= (qrencode--svg-path [[1 1] [1 1]] 0) "M0 0h2v1h-2zM0 1h2v1h-2z"))) + (should (string= (qrencode--svg-path [[1 1] [1 1]] 0) "M0 0h2v1h-2zM0 1h2v1h-2z")) + (should (string= (qrencode--svg-path [[0 0 1] [0 1 1] [0 0 0]] 0) "M2 0h1v1h-1zM1 1h2v1h-2z"))) (defun qrencode-tests--get-file (filename) "Helper function returning contents of FILENAME." diff --git a/qrencode.el b/qrencode.el index 663146e..dbde961 100644 --- a/qrencode.el +++ b/qrencode.el @@ -1057,35 +1057,26 @@ Optionally specify PIXEL-SIZE (default is 3)." (with-temp-buffer (let ((size (length qr))) (dotimes (r size) - (let ((start 0) - (prev 0)) - (dotimes (c size) - ;; We only bother scanning one row at a time and turning - ;; series of 1's into a rectangle. - (let ((current (qrencode--aaref qr c r)) - (do-draw nil)) - (if (and (/= current prev) (= prev 1)) - (setq do-draw t) ;; draw if we change to 0 - (when (and (= c (1- size)) (= current 1)) ;; draw last pixel - (setq do-draw t - c (1+ c)) - (when (/= prev 1) - (setq start (1- c))))) - (when do-draw - (let ((block-len (- c start))) - (insert - ;; `Mx y` move to x,y absolute - ;; `hx` draw x horizontal relative - ;; `v1` draw 1 vertical relative - ;; `z` close - (format "M%d %dh%dv1h-%dz" - (+ start quiet-zone-size) - (+ r quiet-zone-size) - block-len - block-len)))) - (when (or do-draw (/= current prev)) - (setq prev current - start c))))))) + (let ((c 0)) + (while (< c size) + (if (= (qrencode--aaref qr c r) 1) + (let ((start c)) + ;; We only bother scanning one row at a time and turning + ;; series of 1's into a rectangle. + (while (and (< c size) (= (qrencode--aaref qr c r) 1)) + (setq c (1+ c))) + (let ((block-len (- c start))) + (insert + ;; `Mx y` move to x,y absolute + ;; `hx` draw x horizontal relative + ;; `v1` draw 1 vertical relative + ;; `z` close + (format "M%d %dh%dv1h-%dz" + (+ start quiet-zone-size) + (+ r quiet-zone-size) + block-len + block-len)))) + (setq c (1+ c))))))) (buffer-string))) (defun qrencode-as-svg (qr &optional pixel-size inverse) From b42428873f287c60992b671fe03fd24eefd34e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= Date: Sun, 27 Sep 2026 00:48:58 +0100 Subject: [PATCH 6/6] Update Readme with SVG support and include qr-self.svg. --- README.org | 13 +++++++++---- qr-self.svg | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 qr-self.svg diff --git a/README.org b/README.org index 9cf1937..98eae02 100644 --- a/README.org +++ b/README.org @@ -21,9 +21,10 @@ qrencode RET=. QR Codes are rendered as Unicode text, but there is an option to - export them as bitmap (NetPBM format) by pressing =e= in the QR Code - buffer. There are also some public elisp library functions to - generate QR Codes for use in other elisp code. + export them as bitmap (NetPBM format) or vector graphics (SVG) by + pressing =e= in the QR Code buffer. There are also some public + elisp library functions to generate QR Codes for use in other elisp + code. Currently two NetPBM formats are supported: P1 and P4. The latter is much smaller and the exporter much faster, so highly recommended. @@ -32,6 +33,10 @@ format can be changed. In the next major version release P1 support is likely to get dropped in favour of P4. + SVG export is available if =svg.el= (Emacs 26.1 or newer) is + available. This can be selected by customizing + =qrencode-export-format= to ='svg=. + *** Converting different bitmaps This package only supports exporting to NetPBM format due to the @@ -60,7 +65,7 @@ [[file:https://github.com/ruediger/qrencode-el/actions/workflows/test.yml/badge.svg]] - The code is written in pure Emacs Lisp on GNU Emacs 27. It should + The code is written in pure Emacs Lisp on GNU Emacs 31. It should be backwards compatible to GNU Emacs 25.1 and potentially even earlier with =seq= and =cl-lib= compat libraries. diff --git a/qr-self.svg b/qr-self.svg new file mode 100644 index 0000000..0d52712 --- /dev/null +++ b/qr-self.svg @@ -0,0 +1 @@ +