|
From: | Jean Abou Samra |
Subject: | Fun with Python... |
Date: | Fri, 10 Feb 2023 03:39:22 +0100 |
User-agent: | Evolution 3.46.3 (3.46.3-1.fc37) |
Hi,
I just want to share a little adventure.
For context: I was not very satisfied with the snippet https://lsr.di.unimi.it/LSR/Item?id=1153 because its interface is
\markup \qr-code "<big block of data generated externally>"
rather than
\markup \qr-code "https://my-url.com"
I was too lazy to reimplement QR-code encoding (with
error-correcting codes etc.). I decided to take a look
at getting the QR code data directly from Python. Of
course, you can do that with a script and the system
or
system*
function to run the python
command. But
partly out of curiosity, and partly because that
has some problems (like complicated permission issues in
sandboxed environments), I was interested in doing that
by actually using Python in-process, much like Scheme
is being used, which is possible with Guile's FFI
and Python's C API.
The sad outcome is that this requires Guile 3 features
(load-foreign-library
, with its #:global?
argument),
while LilyPond is currently on Guile 2, so nobody can use
it yet apart from a few people who, like me, run a version
of LilyPond self-compiled with Guile 3.
Still, seeing that this sort of thing is even possible, and will eventually become "really" possible when LilyPond (hopefully) switches to Guile 3, leaves me pensive.
\version "2.25.2"
#(use-modules (system foreign)
(system foreign-library)
(srfi srfi-111))
run-python =
#(let ()
(define void (@ (system foreign) void))
(define python (load-foreign-library "libpython3.11" #:global? #t))
(define-syntax-rule (wrap fn (args ...) ret)
(define fn
(pointer->procedure
ret
(dynamic-func (symbol->string 'fn) python)
(list args ...))))
(wrap Py_NewRef ('*) '*)
(wrap Py_DecRef ('*) void)
(wrap PyRun_String ('* int '* '*) '*)
(wrap Py_Initialize () void)
(wrap PyImport_AddModule ('*) '*)
(wrap PyModule_GetDict ('*) '*)
(wrap PyObject_Str ('*) '*)
(wrap PyUnicode_FromString ('*) '*)
(wrap PyUnicode_AsUTF8 ('*) '*)
(wrap PyErr_Print () void)
(wrap PyDict_GetItemString ('* '*) '*)
(wrap PyDict_SetItemString ('* '* '*) int)
(define Py_file_input 257)
(define _initialized (Py_Initialize))
(define __main__ (string->pointer "__main__" "UTF-8"))
(define lyval (string->pointer "lyval" "UTF-8"))
(define lyinput (string->pointer "lyinput" "UTF-8"))
(define main-module
(let ((ret (PyImport_AddModule __main__)))
(when (null-pointer? ret)
(PyErr_Print)
(error "Python initialization of __main__ failed"))
ret))
(define main-dict (PyModule_GetDict main-module))
(define (run-python code input)
(let ((c-string (string->pointer code "UTF-8"))
(c-input (string->pointer input "UTF-8")))
(PyDict_SetItemString main-dict lyinput (PyUnicode_FromString c-input))
(let ((result-obj (PyRun_String c-string Py_file_input main-dict main-dict)))
;; FIXME: handling exception still prints the Python backtrace
(when (null-pointer? result-obj)
(PyErr_Print)
(error "Error while running Python code"))
(let ((lyval-ret (PyDict_GetItemString main-dict lyval)))
(if (null-pointer? lyval-ret)
(error "lyval not defined by code")
(let ((lyval-str (PyUnicode_AsUTF8 lyval-ret)))
(if (null-pointer? lyval-str)
(error "non-string lyval not supported")
;; not sure what should(n't) be decreffed
(pointer->string lyval-str -1 "UTF-8"))))))))
run-python)
#(define (get-qrcode-matrix url)
(run-python
"
import pyqrcode
qr = pyqrcode.create(lyinput)
lyval = qr.text(quiet_zone=0)
"
url))
#(define (index-map f . lsts)
"Applies @code{f} to corresponding elements of @code{lists}, just as @code{map},
providing an additional counter starting at zero. @code{f} needs to have the
counter in its arguments like @code{(index-map (lambda (i arg) <body>) lists)}"
(let loop ((lsts lsts)
(acc '())
(i 0))
(if (any null? lsts)
(reverse! acc)
(loop (map cdr lsts)
(cons (apply f i (map car lsts))
acc)
(1+ i)))))
#(define-markup-command (qr-code layout props url) (string?)
#:properties ((width 10))
(let* ((data (get-qrcode-matrix url))
;; Return lines in reversed order, since translating in Y-axis
;; uses increasing values. Meaning lines will be stacked upwards.
(lines (reverse
(remove
string-null?
(map string-trim-both (string-split data #\newline)))))
(n (length lines))
(square-width (/ width n))
(box (make-filled-box-stencil `(0 . ,square-width)
`(0 . ,square-width))))
;; Build the final qr-code-stencil from line-stencils list
(apply ly:stencil-add
;; Get a list of line-stencils
(index-map
(lambda (i line)
;; Build a line-stencil from square-stencils list
(apply ly:stencil-add
;; Get a list of (already translated) square-stencils
;; per line
(index-map
(lambda (j char)
(ly:stencil-translate
(stencil-with-color
box
(case char
((#\0)
white)
((#\1)
black)
(else
(ly:warning
"unrecognized character ~a, should be 0 or 1"
char)
red)))
(cons (* j square-width)
(* i square-width))))
(string->list line))))
lines))))
\markup \qr-code "https://lilypond.org"
signature.asc
Description: This is a digitally signed message part
[Prev in Thread] | Current Thread | [Next in Thread] |