lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Fun with Python...


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"

qrcode.png

Attachment: signature.asc
Description: This is a digitally signed message part


reply via email to

[Prev in Thread] Current Thread [Next in Thread]