Exporting beamer presentations
March, 2015

Note. It is best to read the original TeXmacs file with interactive Scheme sessions where one may experiment with the code. It can be found in the project's source code, inside the directory web/miguel.

Currently (SVN 8900) exporting documents in beamer style to PDF is not always quite what one would like. Folds, unrolls & co. are all flattened out before export happens, which is great when one wants to distribute a presentation, but no so much if one needs a PDF to actually do the presentation. Also, page numbering does not work and nested switches fail to export correctly. In this short document we set to fix this.

Note. Chances are we will be committing most of this code soon, so if you are running the latest SVN version or a version when it comes out, then this document is no more than a walk through the implementation details of something you already have.

tl;dr. 1. Run all the code in this file. 2. Open your presentation. 3. Click on ToolsExecuteEvaluate scheme expression, enter the command (create-slides (current-buffer)) and run it. 4. A new buffer opens with the slides: create a preamble and add the macros listed below. 5. Click on FileExportPdf…

The implementation is easy (though not exactly optimized). We assume that a buffer is open which contains a screens tree.

  1. Create a new buffer with a root node screens .

    1. This is needed in order for the slide numbers to be correctly computed, since the routines doing it rely on the existence of a screens tree.

    1 Copy the styles, metadata, preamble and initial environment from the original buffer .

  2. Rewind the tree of (i.e. emulate a click in DynamicFirst).

  3. For each child of screens (which we call a screen) in do the following:

    1. Output a slide with the current screen to .

    2. Advance buffer (i.e. emulate a click in DynamicNext).

    3. Are there any more steps in the current screen?

      If not, then stop (and process the next screen), otherwise go to ?.

  4. If we came from FileExportPdf…, then write the PDF, then kill . Otherwise switch-to-buffer and let the user decide.

The reason why we go screen by screen is to produce a new document with a screens tag with the same number of children as the original one. This is needed for the page count.

Figure 1. The original buffer and the result after the slides have been created.

First of all a couple of handy macros:

Scheme]

(define-public-macro (nnull-with var val . body)

; assign @val to @var and execute @body only if @val is not a null list

‘(with ,var ,val

(if (nnull? ,var) (begin ,@body) #f)))

Scheme]

(define (list->tree label l)

(tree-insert (tree label) 0 l))

Scheme]

1.Removing hidden children

Although not strictly necessary, we prune hidden children of switch tags (and related ones) in order to reduce the complexity of the document we produce in the end. Note that this function is run on the children of screens, so we don't risk erasing the hidden ones. Furthermore, alternative-context? excludes tags like unroll or overlay whose hidden children should tipically not be deleted.

Scheme]

(define (keep-shown! t)

(if (alternative-context? t)

(map ; Prune hidden children

(lambda (i) (if (tm-func? (tree-ref t i) 'hidden)

(tree-remove! t i 1)))

(reverse (.. 0 (tm-arity t)))))

(if (and (tm-compound? t) (> (tm-arity t) 0))

(for-each keep-shown! (tree-children t))))

Scheme]

2.The stop condition

How do we know when we are done “clicking” DynamicNext? A simple way is the following: at each processing step (i.e. after each “click”) we check whether the current screen being processed has just been hidden. This leaves the problem of the last screen though, and a (admittedly a bit hackish) solution is to add a fake last child to the original screens which will be hidden to start with. Then we exclude this one from the list of screens to process and all is well.

3.Creating the individual slides

There isn't much to say here which we hadn't said already. As usual, start reading at the last function.

Scheme]

(define (create-slide scr)

(if (not (tree? scr)) (tree 'slide '(document "")) ; just in case

(with t (tree-copy scr)

(keep-shown! t)

(tree-assign-node! t 'slide)

t)))

Scheme]

(define (process-screen scr)

(cons (create-slide scr)

(begin

(dynamic-traverse-buffer :next)

(if (tm-func? scr 'hidden) '()

(process-screen scr)))))

Scheme]

(define (screens->slides t)

(if (not (tm-func? t 'screens)) (tree 'document "")

(with f (lambda (scr) (list->tree 'document (process-screen scr)))

(system-wait "Generating slides" "please wait")

; Insert fake screen at the end

(tree-insert! t (tree-arity t)

(list (tree 'hidden '(document ""))))

(dynamic-operate-on-buffer :first)

; Notice that we don't process the last (fake) screen

(list->tree 'screens (map f (cDr (tree-children t)))))))

Scheme]

4.Bringing it all together

Recall that we intend to create a new buffer with the slides. In order to preserve the preamble and other attributes of the original document we copy all of it, then edit out what we don't want. The copying is done in buffer-copy below, and create-slides uses it and adds the finishing touches.

Scheme]

(use-modules (utils library cursor)) ; defines with-buffer

Scheme]

(define (buffer-copy buf)

"Creates a copy of @u and returns the new url."

(with-buffer buf

(let* ((u (buffer-new))

(styles (get-style-list))

(init (get-all-inits))

(body (tree-copy (buffer-get-body buf))))

(view-new u) ; needed by buffer-focus, used in with-buffer

(buffer-set-body u body)

(with-buffer u

(set-style-list styles)

(init-env "global-title" (buffer-get-metadata buf "title"))

(init-env "global-author" (buffer-get-metadata buf "author"))

(init-env "global-subject" (buffer-get-metadata buf "subject"))

(for-each

(lambda (t)

(if (tree-func? t 'associate)

(with (var val) (list (tree-ref t 0) (tree-ref t 1))

(init-env-tree (tree->string var) val))))

(tree-children init)))

u)))

Scheme]

(tm-define (create-slides buf)

(nnull-with l (select (buffer->tree buf) '(screens))

(let* ((scrns (car l))

(new-buf (buffer-copy buf))

(saved (tree-copy (buffer-get-body buf)))

(slides (screens->slides scrns))) ; modifies buf

(buffer-set-body buf saved) ; restore original buf

(with t (car (select (buffer->tree new-buf) '(screens)))

(tree-set! t slides)) ; Replace body with new content

(switch-to-buffer new-buf)

(init-env "page-medium" "paper")

(add-style-package "slides"))))

((guile-user))

Scheme]

5.Not quite there yet

Before you can use (create-slides (current-buffer)) there are two things to be done. First you'll want to add a couple of definitions to the style file packages/beamer/slides.ts. Alternatively you can just add them to the preamble of the new buffer. Notice that the first one disables the grey bar of links to the slide numbers.

<document|<assign|screens-bar|<macro|body|>>|<assign|page-odd-footer|>|<assign|page-even-footer|>|<assign|page-odd-header|>|<assign|page-even-header|>>

Second, we need to skip some hardcoded behaviour. Currently, there's C++ code in the routine exporting to PDF which checks whether we are using the beamer style, and if we are it creates a copy of the buffer, runs dynamic-make-slides on it, then prints the results. But dynamic-make-slides (who flattens out the unrolls, folds, etc.) is precisely what we wanted to avoid! You can work around this problem using TeXmacs' nice contextual overloading:

Scheme]

(define disable-dynamic-make-slides #t)

Scheme]

(tm-define (dynamic-make-slides)

(:require disable-dynamic-make-slides)

(noop))

((guile-user) (dynamic fold-edit))

Scheme]

If you want the old functionality back, just set disable-dynamic-make-slides to #f.

6.Where to go from here

There are a few things which might be cool to do:

  1. A table of contents for the generated PDF would be great.

  2. Fine grained control of what environments should be flattened out, if any.

  3. Right now images linked in the original document using relative paths will not display properly in the new buffer unless it is saved in the same folder as the original. We could fix this either including the images or pre-saving the buffer in the right folder.