Generating svgs using the html renderer for Scribble

Hi All,

The the html renderer for Scribble has a keyword argument #:image-preferences.
The argument can be used to pass a list of preferred image formats to Scribble,
such as svg, png etc.

In the example program below, I am passing a pict to the html renderer.
Despite passing (svg) as the list of preferred image formats, the
program generates a png file.

What have I overlooked? Is there an additional parameter I need to set?

#lang racket
(require scribble/render scribble/manual scribble/core scribble/decode
         scribble/base-render scribble/xref
         (prefix-in html: scribble/html-render)
         setup/xref)

(define no-style (style "from_scribble" '()))

; The result of prepare is a list of parts.

(define (block->part       x)  (part #f '() #f no-style '() (list x) '()))
(define (blocks->part      xs) (part #f '() #f no-style '()       xs '()))

(define (content->block    x)  (paragraph no-style  x))
(define (contents->block   xs) (paragraph no-style xs))
(define (content->part     x)  (block->part (content->block  x)))
(define (contents->part    xs) (block->part (contents->block xs)))

(define (element->paragrah x)  (paragraph no-style x))
(define (paragraph->part   x)  (block->part   x))
(define (element->part     x)  (paragraph->part (element->paragrah x)))

(define (prepare x)
  (define who 'prepare)
  (cond
    [(part?    x)    (list x)]
    [(block?   x)    (list (block->part x))]
    [(box-splice? x) (map prepare (splice-run x))]
    [(list?    x)    (prepare* x)]
    [(element? x)    (when (eq? (element-style x) 'tt)
                       (newline)(newline) (write x) (newline)(newline))
                     (element->part x)]
    [(content? x)    (list (content->part x))]     
    [else            (error who (~a "unexpected input, got: " x))]))

(define (prepare* xs)
  (define who 'prepare*)
  (cond
    [(andmap part?    xs) xs]
    [(andmap block?   xs) (list (blocks->part   (prepare* xs)))]
    [(andmap content? xs) (list (contents->part (prepare* xs)))]
    
    [(list? xs) (append-map prepare xs)]
    [else       (prepare xs)]))


(define (render docs
                names
                ; #:render-mixin [render-mixin html:render-mixin]
                #:dest-dir           [dest-dir           #f]
                #:helper-file-prefix [helper-file-prefix #f]
                #:prefix-file        [prefix-file        #f]
                #:style-file         [style-file         #f]
                #:style-extra-files  [style-extra-files  null]
                #:extra-files        [extra-files        null]
                #:image-preferences  [image-preferences  null]
                ; redirects      sets   set-external-tag-path
                #:redirect           [redirect           #f]  
                ; redirect-main  sets   set-external-root-url
                #:redirect-main      [redirect-main      #f]  
                #:directory-depth    [directory-depth    0]
                #:xrefs              [xrefs              null]
                #:info-in-files      [info-input-files   null]
                #:info-out-file      [info-output-file   #f]
                #:quiet?             [quiet?             #t]
                #:warn-undefined?    [warn-undefined?    (not quiet?)])
  ; (set! docs (map prepare docs))
  (define out #f)
  (define render-mixin html:render-mixin)
  ; (when dest-dir (make-directory* dest-dir))
  (let ([renderer (new (render-mixin render%)
                       [dest-dir           dest-dir]
                       [prefix-file        prefix-file]
                       [style-file         style-file]
                       [style-extra-files  style-extra-files]
                       [extra-files        extra-files]
                       [image-preferences  image-preferences]
                       [helper-file-prefix helper-file-prefix])])
    ; tags are used for cross referencing
    (when redirect
      (send renderer set-external-tag-path redirect))
    (when redirect-main
      (send renderer set-external-root-url redirect-main))
    ; generate multiple files? (0 and 1 is treated the same)
    (unless (zero? directory-depth)
      (send renderer set-directory-depth directory-depth))
    ; if false, writes output-file information to current output port
    (unless quiet?
      (send renderer report-output!))
    ; build filenames from names
    (let* ([fns (map (lambda (fn)
                       (let-values ([(base name dir?) (split-path fn)])
                         (let ([fn (path-replace-suffix
                                    name
                                    (send renderer get-suffix))])
                           (if dest-dir (build-path dest-dir fn) fn))))
                     names)]
           ; 1. Traverse
           [fp   (send renderer traverse docs fns)]
           ; 2. Collect 
           [info (send renderer collect  docs fns fp)])
      ; additional cross-reference information
      (for ([file (in-list info-input-files)])
        (let ([s (with-input-from-file file read)])
          (send renderer deserialize-info s info)))
      ; transfer information to info
      (for ([xr (in-list xrefs)])
        (xref-transfer-info renderer info xr))
      ; 3. Resolve
      ; (match hyperlink references with targets)
      (let ([r-info (send renderer resolve docs fns info)])
        ; 4. Render
        (set! out
              (for/list ([doc docs])
                (send renderer render-part doc #;fns r-info)))
        ; Write cross-reference info to info-output-file
        (when info-output-file
          (let ([s (send renderer serialize-info r-info)])
            (with-output-to-file info-output-file
              #:exists 'truncate/replace
              (lambda () (write s)))))
        ; Maybe warn
        (when warn-undefined?
          (let ([undef (send renderer get-undefined r-info)])
            (unless (null? undef)
              (eprintf "Warning: some cross references may be broken due to undefined tags:\n")
              (for ([t (in-list undef)])
                (eprintf " ~s\n" t))))))
      (append* out))))


(define (embed-scribble x)
  (define docs (flatten (prepare x)))
  (render docs
          (build-list (length docs)
                      (λ(_) (~a "/tmp/fake-filename" (random 10000) ".rkt")))
          #:image-preferences '(svg)
          #:redirect      "https://docs.racket-lang.org/local-redirect/index.html"
          #:redirect-main "http://docs.racket-lang.org/"
          #:xrefs (list (make-collections-xref))))


(require (prefix-in pict: pict))
(embed-scribble (pict:circle 10))

My output:

'((p
   ((class "from_scribble"))
   (img ((style "vertical-align: 0px; margin: -3px -3px -3px -3px;") (src "pict.png") (alt "image") (width "16") (height "16")))))

And the file "pict.png" is created in the same folder as the source file.

The problem is that I am calling the method render-part directly
and that the setup happens in the method render.

(define/override (render ds fns ri)
      (parameterize ([current-render-convertible-requests
                      (sort-image-requests (current-render-convertible-requests)
                                           image-preferences)])
        (render-top ds fns ri)))

So either I need a way to parameterize current-render-convertible-requests
my self (but it isn't exported) or put the part into something that render can receive.

Parameterizing current-render-convertible-requests worked.

(require rackunit)
(require/expose scribble/html-render (current-render-convertible-requests))

(define (embed-scribble x)
  (define docs (flatten (prepare x)))
  (parameterize ([current-render-convertible-requests '(svg-bytes)])
    (render docs
            (build-list (length docs)
                        (λ(_) (~a "/tmp/fake-filename" (random 10000) ".rkt")))
            #:image-preferences '(svg)
            #:redirect      "https://docs.racket-lang.org/local-redirect/index.html"
            #:redirect-main "http://docs.racket-lang.org/"
            #:xrefs (list (make-collections-xref)))))
2 Likes