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](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
