Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions qr-self.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 30 additions & 9 deletions qrencode-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,13 @@ 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"))
(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."
(with-temp-buffer
Expand All @@ -553,7 +560,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))
Expand All @@ -564,18 +572,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 "<svg " svg)))
;; TODO: test sizes.
;; TODO: test pixel equivalence between P1 and P4.
)
(delete-file tmpfile-p1)
(delete-file tmpfile-p4)))))
(delete-file tmpfile-p4)
(delete-file tmpfile-svg)))))

(ert-deftest qrencode-zbarimg-test ()
"Test decoding generated QRCodes using the zbarimg program."
(let ((zbarimg (executable-find "zbarimg")))
(skip-unless zbarimg)
(let ((tmpfile-p1 (make-temp-file "qr-p1" nil ".pbm"))
(tmpfile-p4 (make-temp-file "qr-p4" nil ".pbm")))
(tmpfile-p4 (make-temp-file "qr-p4" nil ".pbm"))
(tmpfile-svg (make-temp-file "qr-svg" nil ".svg"))
qr)
(unwind-protect
(cl-loop for input across
["hello"
Expand All @@ -588,14 +604,19 @@ The expected word for version 7 is the literal from section 7.10 of the
;; raw UTF-8 characters
"😸🚗愛"
]
do (qrencode--write-as-netpbm-p4 tmpfile-p4 (qrencode input nil nil 'return-raw))
do (setq qr (qrencode input nil nil 'return-raw))
do (qrencode--write-as-netpbm-p4 tmpfile-p4 qr)
do (with-temp-file tmpfile-p1
(insert (qrencode-format-as-netpbm (qrencode input nil nil 'return-raw))))
do (dolist (tmpfile (list tmpfile-p1 tmpfile-p4))
(should (string= (shell-command-to-string (format "%s -q '%s'" zbarimg tmpfile))
(format "QR-Code:%s\n" input)))))
(insert (qrencode-format-as-netpbm qr)))
do (when (featurep 'svg)
(qrencode--write-as-svg tmpfile-svg qr))
do (dolist (tmpfile (list tmpfile-p1 tmpfile-p4 (when (featurep 'svg) tmpfile-svg)))
(when tmpfile
(should (string= (shell-command-to-string (format "%s -q '%s'" zbarimg tmpfile))
(format "QR-Code:%s\n" input))))))
(delete-file tmpfile-p1)
(delete-file tmpfile-p4)))))
(delete-file tmpfile-p4)
(delete-file tmpfile-svg)))))

(provide 'qrencode-tests)
;;; qrencode-tests.el ends here
67 changes: 64 additions & 3 deletions qrencode.el
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

;; Author: Rüdiger Sonderfeld <ruediger@c-plusplus.net>
;; Keywords: qrcode comm
;; Version: 1.5
;; Version: 1.6-beta1
;; Package-Requires: ((emacs "25.1"))
;; Package: qrencode
;; URL: https://github.com/ruediger/qrencode-el
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1043,6 +1046,61 @@ 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
(let ((size (length qr)))
(dotimes (r size)
(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)
"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")
Expand Down Expand Up @@ -1073,9 +1131,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)

Expand All @@ -1094,6 +1153,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)
Expand Down
Loading