My org-beamer setup
In the beginning was FoilTeX…
I started using LaTeX around 1995. For making slides1 I
initially used FoilTeX, briefly switched to prosper around 2004
(I'm pretty sure I also tried powerdot at some point) before going
back to FoilTeX, but with a custom myfoils.sty style file supposed
to allow a lighter and more convenient markup:
- a
slideenvironment, -
abbreviations for very common markup:
\bi\ei\ifor itemizes,\be\eefor enumerate,\bmp\empfor a minipage,\imgfor including a picture.
This is also the time I decided that since a lot of my slides were
vertically split between text and a picture, it would be nice to have
lslide and rslide environments with the name of a picture file as
an additional argument (where wicp.pdf is a picture file):
\begin{rslide}{wcip}{WCIP}
the width of the picture could be specified by an optional argument
(\lw stands for linewidth):
\begin{rslide}[.4\lw]{307}{Redirection : \emph{application redirect}}
I still use a slightly modified version of these environments, as I discuss later. Again, this allowed less verbose markup. Since I'm using emacs, I could have defined macros to quickly insert such code templates, but the point was also (perhaps mainly, actually) to be visually lighter.
Browsing through old files to rebuild this history, I was quite
surprised that I apparently used foils until 2014, when I finally
switched to beamer. For doing so, I translated myfoils.sty to
mybeamer.sty, with one more slide env, slidev, mapped to a frame
env marked as fragile (since beamer requires that for slides
containing verbatim text).
Somehow, I had a loose aim to be as independant as possible from the precise "backend" format used.
The current setup
Objectives
Nowadays, my eyes are tired of LaTeX, so I write most of my presentations in org with emacs, and export them to pdf with the org-beamer exporter. There are three features I need to work confortably:
- Easily define slides that are vertically split between some text and a picture.
- Include picture animations, as produced from
xfigby my mfig2dev tool. -
Ability to easily produce three different documents for a given presentation:
pres: the slides with speaker notes (obviously),handout: a handout version (no animations, several slides per page),slides: the slides without speaker notes (so that students might be able to re-see the slides exactly as they were shown during the lecture).
Vertically split slides
On the LaTeX side, my beamer-mfig LaTeX package2 defines the following two environments:
\begin{frameL}[r]{pict}{title}
\begin{frameR}[r]{pict}{title}
Here pict is the name of the picture file (without extension) and
r is the width ratio of the picture (0.5 means the picture will
occupy half of the slide).
Now to use that from org, I need a way to tell org about the
environment to use (eg frameR) together with the picture name and
the width ratio.
The org-beamer exporter uses the BEAMER_frame property (processed in
function org-beamer--format-frame in file ox-beamer.el) to specify
the environement to use for a specific frame, but it doesn't allow any
arguments to be passed to the environment. Here's an example where I
use a custom poll LaTeX environment displaying a poll slide (the
nohandout tag will ensure this is skipped when exporting to the
handout):
** Reverse proxy :nohandout:
:PROPERTIES:
:BEAMER_frame: poll
:END:
A client request arrives at P, w/ directive =Cache-Control: no-cache=.
P has fresh cached response.
1. P should serve cached response
2. P should forward request to O
3. it depends...What I need here is to be able to write this:
** What's a CDN (technical)
:PROPERTIES:
:BEAMER_frame: frameR cdn_systemes-en 0.47
:END:
- bring content close to the client
- "to the edge"
- 4 systems
- delivery
- redirection
- distribution
- accountingand get it exported as
\begin{frameR}[0.47]{cdn_systemes-en}{What's a CDN (technical)}
For this I need to modify org-beamer--format-frame so that it gets
the env name as well as possibly two args to the env, and then
inserts these args (second as a LaTeX optional arg, first as a LaTeX
mandatory arg). Here's the result (changes are marked by triple
semicolon comments):
(defun org-beamer--format-frame (headline contents info)
"Format HEADLINE as a frame.
CONTENTS holds the contents of the headline. INFO is a plist
used as a communication channel."
(let* ((fragilep
;; FRAGILEP is non-nil when HEADLINE contains an element
;; among `org-beamer-verbatim-elements'.
(org-element-map headline org-beamer-verbatim-elements 'identity
info 'first-match))
;; If FRAGILEP is non-nil and CONTENTS contains an occurrence
;; of \begin{frame} or \end{frame}, then set the FRAME
;; environment to be `org-beamer-frame-environment';
;; otherwise, use "frame". If the selected environment is not
;; "frame", then add the property :beamer-define-frame to
;; INFO and set it to t.
;;; replace
;;; (frame (let ((selection
;;; with
;; we want to get values for arg1 and arg2 in addition to frame
(frame-arg (or (org-element-property :BEAMER_FRAME headline)
(let ((selection
;;; end
(or (and fragilep
(or (string-match-p "\\\\begin{frame}" contents)
(string-match-p "\\\\end{frame}" contents))
org-beamer-frame-environment)
"frame")))
(unless (string= selection "frame")
(setq info (plist-put info :beamer-define-frame t)))
selection)))
;;; add
(split (split-string frame-arg " "))
(frame (car split))
(arg1 (cadr split))
(arg2 (caddr split)))
;;; end
(concat "\\begin{" frame "}"
;; Overlay specification, if any. When surrounded by
;; square brackets, consider it as a default
;; specification.
(let ((action (org-element-property :BEAMER_ACT headline)))
(cond
((not action) "")
((string-match "\\`\\[.*\\]\\'" action )
(org-beamer--normalize-argument action 'defaction))
(t (org-beamer--normalize-argument action 'action))))
;; Options, if any.
(let* ((beamer-opt (org-element-property :BEAMER_OPT headline))
(options
;; Collect nonempty options from default value and
;; headline's properties.
(cl-remove-if-not #'org-string-nw-p
(append
(org-split-string
(plist-get info :beamer-frame-default-options) ",")
(and beamer-opt
(org-split-string
;; Remove square brackets if user provided
;; them.
(and (string-match "^\\[?\\(.*\\)\\]?$" beamer-opt)
(match-string 1 beamer-opt))
",")))))
(fragile
;; Add "fragile" option if necessary.
(and fragilep
(not (member "fragile" options))
(list "fragile")))
(label
;; Provide an automatic label for the frame unless
;; the user specified one. Also refrain from
;; labeling `allowframebreaks' frames; this is not
;; allowed by Beamer.
(and (not (member "allowframebreaks" options))
(not (cl-some (lambda (s) (string-match-p "^label=" s))
options))
(list
(let ((label (org-beamer--get-label headline info)))
;; Labels containing colons need to be
;; wrapped within braces.
(format (if (string-match-p ":" label)
"label={%s}"
"label=%s")
label))))))
;; Change options list into a string.
;;; replace
;;; (org-beamer--normalize-argument
;;; (mapconcat #'identity (append label fragile options) ",")
;;; 'option))
;;; with
;; Don't add options if there's already an arg
(if arg1 "" (org-beamer--normalize-argument
(mapconcat #'identity (append label fragile options) ",")
'option)))
;; insert args if any
(if arg2 (concat "[" arg2 "]") "")
(if arg1 (concat "{" arg1 "}") "")
;;; end
;; Title.
(let ((env (org-element-property :BEAMER_ENV headline)))
(format "{%s}"
(if (and env (equal (downcase env) "fullframe")) ""
(org-export-data
(org-element-property :title headline) info))))
"\n"
;; The following workaround is required in fragile frames
;; as Beamer will append "\par" to the beginning of the
;; contents. So we need to make sure the command is
;; separated from the contents by at least one space. If
;; it isn't, it will create "\parfirst-word" command and
;; remove the first word from the contents in the PDF
;; output.
(if (not fragilep) contents
(replace-regexp-in-string "\\`\n*" "\\& " (or contents "")))
"\\end{" frame "}")))I decided to modify the function which is kind of a quick hack, I could have instead advised it. Maybe at some point the official version will allow for this kind of things…
Picture animations
This is implemented in LaTeX with the frameLs and frameRs
environments from the beamer-mfig package, so again org needs to be
provided the correct environment and arguments to use when exporting.
Just like above the necessary information can be provided through the
"extended" BEAMER_frame property.
** EDNS Client-subnet extension
:PROPERTIES:
:BEAMER_frame: frameRs edns:,1,2,3,4 0.6
:END:
#+ATTR_BEAMER: :overlay <+->
1. =www.example.com A ?=
2. =www.example.com A ? [client-subnet = 192.168.130.0/24]=
3. =www.example.com A 176.177.1.1 [client-subnet = 192.168.0.0/16]=
4. =www.example.com A 176.177.1.1=Exporting
So I need to run org export with different series of parameters, according to the specific output document desired. I could put all settings in the org file and use some elisp to adjust it at each export, but I don't think it's reasonable to require the file to be changed and saved just to compile it again.
Currently this is implemented this way:
- Write three different org files containing the setup for each
output version:
setup-pres.org,setup-slides.org,setup-handout.org. - Ensure
setup.orgis a link to one of the three above files, the currently selected one. - The main org file contains a
#+INCLUDE: ./setup.orgline to read its setup.
The following elisp code defines the my-org-beamer-export command
that selects the output version based on its prefix argument (set the
setup.org link to the desired setup file), produces the output by
using org export, and finally renames the file. This command is bound
to the C-c p key.
(defun my--org-beamer-link-setup (kind)
(case kind
(handout (delete-file "setup.org")
(add-name-to-file "setup-handout.org" "setup.org"))
(slides (delete-file "setup.org")
(add-name-to-file "setup-slides.org" "setup.org"))
(pres (delete-file "setup.org")
(add-name-to-file "setup-pres.org" "setup.org"))))
(defun my--org-beamer-do-export (basename kind)
(my--org-beamer-link-setup kind)
(org-beamer-export-to-pdf)
(rename-file (concat basename ".pdf")
(format "%s-%s.pdf" basename kind) t))
(defun my-org-beamer-export (arg)
(interactive "p")
(let ((kind
(case arg
(0 'all)
(1 'pres)
(4 'handout)
(16 'slides)))
(basename (file-name-sans-extension (buffer-name))))
(if (eq kind 'all)
(progn
(my--org-beamer-do-export basename 'pres)
(my--org-beamer-do-export basename 'slides)
(my--org-beamer-do-export basename 'handout))
(my--org-beamer-do-export basename kind))))
(add-hook 'org-beamer-mode-hook
(lambda ()
(define-key org-beamer-mode-map "\C-cp" #'my-org-beamer-export)))
If you're not fluent in emacs-lisp, C-c p generates main-pres.pdf,
C-u C-c p generates main-handout.pdf, and C-u C-u C-c p
generates main-slides.pdf. Also C-u 0 C-c p generates all three
outputs.
The only thing I don't like is the need to maintain/carry three setup files in addition to the main file. This is not so bad as the setup files are typically shared between a set of slides files, but I'd rather have a single setup file containing the settings for all versions. Ideally, there could be some conditional inclusion controlled by an elisp variable, but I currently don't know how to do that.
Well, even if I'm not entirely happy with that, it does the job. It's really nice to be able to generate the desired version (or all of them) with that few key strokes!