That's pretty cool, and sets a template for those who are brave/foolish enough to build extensions of all sorts in Python.
Nice job! What version of Lilypond will that appear in? On Thu, Feb 9, 2023 at 9:40 PM Jean Abou Samra <[email protected]> wrote: > 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" > > [image: qrcode.png] > -- *Jeff Kopmanis* Medium: https://kopmanis.medium.com GLAAC: https://www.glaac.org/ University Lowbrow Astronomers: http://umich.edu/~lowbrows Orange Can Astronomy: https://www.facebook.com/orangecanastronomy/ ** Go Green and leave this email on the Screen! **
