about summary refs log tree commit diff
path: root/src/guile/skribilo/engine
diff options
context:
space:
mode:
authorLudovic Court`es2006-10-11 07:43:47 +0000
committerLudovic Court`es2006-10-11 07:43:47 +0000
commitd4360259d60722eaa175a483f792fce7b8c66d97 (patch)
tree622cc21b820e3dd4616890bc9ccba74de6637d8a /src/guile/skribilo/engine
parentfc42fe56a57eace2dbdb31574c2e161f0eacf839 (diff)
downloadskribilo-d4360259d60722eaa175a483f792fce7b8c66d97.tar.gz
skribilo-d4360259d60722eaa175a483f792fce7b8c66d97.tar.lz
skribilo-d4360259d60722eaa175a483f792fce7b8c66d97.zip
slide: Propagate the `outline?' parameter in `slide-(sub)?topic'.
* src/guile/skribilo/package/slide.scm (slide-topic): Propagate the
  `outline?' parameter as an option.
  (slide-subtopic): Likewise.

git-archimport-id: skribilo@sv.gnu.org--2006/skribilo--devo--1.2--patch-1
Diffstat (limited to 'src/guile/skribilo/engine')
-rw-r--r--src/guile/skribilo/engine/Makefile.am5
-rw-r--r--src/guile/skribilo/engine/base.scm479
-rw-r--r--src/guile/skribilo/engine/context.scm1382
-rw-r--r--src/guile/skribilo/engine/html.scm2313
-rw-r--r--src/guile/skribilo/engine/html4.scm168
-rw-r--r--src/guile/skribilo/engine/latex-simple.scm103
-rw-r--r--src/guile/skribilo/engine/latex.scm1784
-rw-r--r--src/guile/skribilo/engine/lout.scm2891
-rw-r--r--src/guile/skribilo/engine/xml.scm115
9 files changed, 9240 insertions, 0 deletions
diff --git a/src/guile/skribilo/engine/Makefile.am b/src/guile/skribilo/engine/Makefile.am
new file mode 100644
index 0000000..7b6ec2c
--- /dev/null
+++ b/src/guile/skribilo/engine/Makefile.am
@@ -0,0 +1,5 @@
+guilemoduledir = $(GUILE_SITE)/skribilo/engine
+dist_guilemodule_DATA = base.scm context.scm html.scm html4.scm	\
+			latex-simple.scm latex.scm		\
+			lout.scm				\
+			xml.scm
diff --git a/src/guile/skribilo/engine/base.scm b/src/guile/skribilo/engine/base.scm
new file mode 100644
index 0000000..8418e8b
--- /dev/null
+++ b/src/guile/skribilo/engine/base.scm
@@ -0,0 +1,479 @@
+;;; base.scm  --  BASE Skribe engine
+;;;
+;;; Copyright 2003, 2004  Manuel Serrano
+;;;
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;; USA.
+
+(define-skribe-module (skribilo engine base))
+
+;*---------------------------------------------------------------------*/
+;*    base-engine ...                                                  */
+;*---------------------------------------------------------------------*/
+(define base-engine
+   (default-engine-set!
+      (make-engine 'base
+		   :version 'plain
+		   :symbol-table '(("iexcl" "!")
+				   ("cent" "c")
+				   ("lguillemet" "\"")
+				   ("not" "!")
+				   ("registered" "(r)")
+				   ("degree" "o")
+				   ("plusminus" "+/-")
+				   ("micro" "o")
+				   ("paragraph" "p")
+				   ("middot" ".")
+				   ("rguillemet" "\"")
+				   ("iquestion" "?")
+				   ("Agrave" "À")
+				   ("Aacute" "A")
+				   ("Acircumflex" "Â")
+				   ("Atilde" "A")
+				   ("Amul" "A")
+				   ("Aring" "A")
+				   ("AEligature" "AE")
+				   ("Oeligature" "OE")
+				   ("Ccedilla" "Ç")
+				   ("Egrave" "È")
+				   ("Eacute" "É")
+				   ("Ecircumflex" "Ê")
+				   ("Euml" "E")
+				   ("Igrave" "I")
+				   ("Iacute" "I")
+				   ("Icircumflex" "Î")
+				   ("Iuml" "I")
+				   ("ETH" "D")
+				   ("Ntilde" "N")
+				   ("Ograve" "O")
+				   ("Oacute" "O")
+				   ("Ocurcumflex" "O")
+				   ("Otilde" "O")
+				   ("Ouml" "O")
+				   ("times" "x")
+				   ("Oslash" "O")
+				   ("Ugrave" "Ù")
+				   ("Uacute" "U")
+				   ("Ucircumflex" "Û")
+				   ("Uuml" "Ü")
+				   ("Yacute" "Y")
+				   ("agrave" "à")
+				   ("aacute" "a")
+				   ("acircumflex" "â")
+				   ("atilde" "a")
+				   ("amul" "a")
+				   ("aring" "a")
+				   ("aeligature" "æ")
+				   ("oeligature" "oe")
+				   ("ccedilla" "ç")
+				   ("egrave" "è")
+				   ("eacute" "é")
+				   ("ecircumflex" "ê")
+				   ("euml" "e")
+				   ("igrave" "i")
+				   ("iacute" "i")
+				   ("icircumflex" "î")
+				   ("iuml" "i")
+				   ("ntilde" "n")
+				   ("ograve" "o")
+				   ("oacute" "o")
+				   ("ocurcumflex" "o")
+				   ("otilde" "o")
+				   ("ouml" "o")
+				   ("divide" "/")
+				   ("oslash" "o")
+				   ("ugrave" "ù")
+				   ("uacute" "u")
+				   ("ucircumflex" "û")
+				   ("uuml" "ü")
+				   ("yacute" "y")
+				   ("ymul" "y")
+				   ;; punctuation
+				   ("bullet" ".")
+				   ("ellipsis" "...")
+				   ("<-" "<-")
+				   ("<--" "<--")
+				   ("uparrow" "^;")
+				   ("->" "->")
+				   ("-->" "-->")
+				   ("downarrow" "v")
+				   ("<->" "<->")
+				   ("<-->" "<-->")
+				   ("<+" "<+")
+				   ("<=" "<=;")
+				   ("<==" "<==")
+				   ("Uparrow" "^")
+				   ("=>" "=>")
+				   ("==>" "==>")
+				   ("Downarrow" "v")
+				   ("<=>" "<=>")
+				   ("<==>" "<==>")
+				   ;; Mathematical operators
+				   ("asterisk" "*")
+				   ("angle" "<")
+				   ("and" "^;")
+				   ("or" "v")
+				   ("models" "|=")
+				   ("vdash" "|-")
+				   ("dashv" "-|")
+				   ("sim" "~")
+				   ("mid" "|")
+				   ("langle" "<")
+				   ("rangle" ">")
+				   ;; LaTeX
+				   ("circ" "o")
+				   ("top" "T")
+				   ("lhd" "<")
+				   ("rhd" ">")
+				   ("parallel" "||")))))
+
+;*---------------------------------------------------------------------*/
+;*    mark ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'symbol
+   :action (lambda (n e)
+	      (let* ((s (markup-body n))
+		     (c (assoc s (engine-symbol-table e))))
+		 (if (pair? c)
+		     (display (cadr c))
+		     (output s e)))))
+
+;*---------------------------------------------------------------------*/
+;*    unref ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'unref
+   :options 'all
+   :action (lambda (n e)
+	      (let* ((s (markup-option n :skribe))
+		     (k (markup-option n 'kind))
+		     (f (cond
+			   (s
+			    (format #f "?~a@~a " k s))
+			   (else
+			    (format #f "?~a " k))))
+		     (msg (list f (markup-body n)))
+		     (n (list "[" (color :fg "red" (bold msg)) "]")))
+		 (skribe-eval n e))))
+
+;*---------------------------------------------------------------------*/
+;*    &the-bibliography ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-bibliography
+   :before (lambda (n e)
+	      (let ((w (markup-writer-get 'table e)))
+		 (and (writer? w) (invoke (writer-before w) n e))))
+   :action (lambda (n e)
+	      (when (pair? (markup-body n))
+		 (for-each (lambda (i) (output i e)) (markup-body n))))
+   :after (lambda (n e)
+	     (let ((w (markup-writer-get 'table e)))
+		(and (writer? w) (invoke (writer-after w) n e)))))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry
+   :options '(:title)
+   :before (lambda (n e)
+	      (invoke (writer-before (markup-writer-get 'tr e)) n e))
+   :action (lambda (n e)
+	      (let ((wtc (markup-writer-get 'tc e)))
+		 ;; the label
+		 (markup-option-add! n :valign 'top)
+		 (markup-option-add! n :align 'right)
+		 (invoke (writer-before wtc) n e)
+		 (output n e (markup-writer-get '&bib-entry-label e))
+		 (invoke (writer-after wtc) n e)
+		 ;; the body
+		 (markup-option-add! n :valign 'top)
+		 (markup-option-add! n :align 'left)
+		 (invoke (writer-before wtc) n e)
+		 (output n e (markup-writer-get '&bib-entry-body))
+		 (invoke (writer-after wtc) n e)))
+   :after (lambda (n e)
+	     (invoke (writer-after (markup-writer-get 'tr e)) n e)))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-label ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-label
+   :options '(:title)
+   :before "["
+   :action (lambda (n e) (output (markup-option n :title) e))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-body ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-body
+   :action (lambda (n e)
+	      (define (output-fields descr)
+		 (let loop ((descr descr)
+			    (pending #f)
+			    (armed #f))
+		    (cond
+		       ((null? descr)
+			'done)
+		       ((pair? (car descr))
+			(if (eq? (caar descr) 'or)
+			    (let ((o1 (cadr (car descr))))
+			       (if (markup-option n o1)
+				   (loop (cons o1 (cdr descr))
+					 pending
+					 #t)
+				   (let ((o2 (caddr (car descr))))
+				      (loop (cons o2 (cdr descr))
+					    pending
+					    armed))))
+			    (let ((o (markup-option n (cadr (car descr)))))
+			       (if o
+				   (begin
+				      (if (and pending armed)
+					  (output pending e))
+				      (output (caar descr) e)
+				      (output o e)
+				      (if (pair? (cddr (car descr)))
+					  (output (caddr (car descr)) e))
+				      (loop (cdr descr) #f #t))
+				   (loop (cdr descr) pending armed)))))
+		       ((symbol? (car descr))
+			(let ((o (markup-option n (car descr))))
+			   (if o
+			       (begin
+				  (if (and armed pending)
+				      (output pending e))
+				  (output o e)
+				  (loop (cdr descr) #f #t))
+			       (loop (cdr descr) pending armed))))
+		       ((null? (cdr descr))
+			(output (car descr) e))
+		       ((string? (car descr))
+			(loop (cdr descr)
+			      (if pending pending (car descr))
+			      armed))
+		       (else
+			(skribe-error 'output-bib-fields
+				      "Illegal description"
+				      (car descr))))))
+	      (output-fields
+	       (case (markup-option n 'kind)
+		  ((techreport)
+		   `(author " -- " (or title url documenturl) " -- "
+			    number ", " institution ", "
+			    address ", " month ", " year ", "
+			    ("pp. " pages) "."))
+		  ((article)
+		   `(author " -- " (or title url documenturl) " -- "
+			    journal ", " volume "" ("(" number ")") ", "
+			    address ", " month ", " year ", "
+			    ("pp. " pages) "."))
+		  ((inproceedings)
+		   `(author " -- " (or title url documenturl) " -- "
+			    booktitle ", " series ", " ("(" number ")") ", "
+			    address ", " month ", " year ", "
+			    ("pp. " pages) "."))
+		  ((book)
+		   '(author " -- " (or title url documenturl) " -- "
+			    publisher ", " address
+			    ", " month ", " year ", " ("pp. " pages) "."))
+		  ((phdthesis)
+		   '(author " -- " (or title url documenturl) " -- " type ", "
+			    school ", " address
+			    ", " month ", " year"."))
+		  ((misc)
+		   '(author " -- " (or title url documenturl) " -- "
+			    publisher ", " address
+			    ", " month ", " year"."))
+		  (else
+		   '(author " -- " (or title url documenturl) " -- "
+			    publisher ", " address
+			    ", " month ", " year ", " ("pp. " pages) "."))))))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-ident ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-ident
+   :action (lambda (n e)
+	      (output (markup-option n 'number) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-title ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-title
+   :action (lambda (n e)
+	      (skribe-eval (bold (markup-body n)) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-publisher ...                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-publisher
+   :action (lambda (n e)
+	      (skribe-eval (it (markup-body n)) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &the-index ...  @label the-index@                                */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-index
+   :options '(:column)
+   :before (lambda (n e)
+	      (output (markup-option n 'header) e))
+   :action (lambda (n e)
+	      (define (make-mark-entry n fst)
+		 (let ((l (tr :class 'index-mark-entry
+			     (td :colspan 2 :align 'left
+				(bold (it (sf n)))))))
+		    (if fst
+			(list l)
+			(list (tr (td :colspan 2)) l))))
+	      (define (make-primary-entry n p)
+		 (let* ((note (markup-option n :note))
+			(b (markup-body n))
+			(c (if note
+			       (list b
+				     (it (list " (" note ")")))
+			       b)))
+		    (when p
+		       (markup-option-add! b :text
+					   (list (markup-option b :text)
+						 ", p."))
+		       (markup-option-add! b :page #t))
+		    (tr :class 'index-primary-entry
+		       (td :colspan 2 :valign 'top :align 'left c))))
+	      (define (make-secondary-entry n p)
+		 (let* ((note (markup-option n :note))
+			(b (markup-body n))
+			(bb (markup-body b)))
+		    (cond
+		       ((not (or bb (is-markup? b 'url-ref)))
+			(skribe-error 'the-index
+				      "Illegal entry"
+				      b))
+		       (note
+			(let ((r (if bb
+				     (it (ref :class "the-index-secondary"
+					    :handle bb
+					    :page p
+					    :text (if p
+						      (list note ", p.")
+						      note)))
+				     (it (ref :class "the-index-secondary"
+					    :url (markup-option b :url)
+					    :page p
+					    :text (if p
+						      (list note ", p.")
+						      note))))))
+			   (tr :class 'index-secondary-entry
+			      (td :valign 'top :align 'right :width 1. " ...")
+			      (td :valign 'top :align 'left r))))
+		       (else
+			(let ((r (if bb
+				     (ref :class "the-index-secondary"
+					:handle bb
+					:page p
+					:text (if p " ..., p." " ..."))
+				     (ref :class "the-index-secondary"
+					:url (markup-option b :url)
+					:page p
+					:text (if p " ..., p." " ...")))))
+			   (tr :class 'index-secondary-entry
+			      (td :valign 'top :align 'right :width 1.)
+			      (td :valign 'top :align 'left r)))))))
+	      (define (make-column ie p)
+		 (let loop ((ie ie)
+			    (f #t))
+		    (cond
+		       ((null? ie)
+			'())
+		       ((not (pair? (car ie)))
+			(append (make-mark-entry (car ie) f)
+				(loop (cdr ie) #f)))
+		       (else
+			(cons (make-primary-entry (caar ie) p)
+			      (append (map (lambda (x)
+					      (make-secondary-entry x p))
+					   (cdar ie))
+				      (loop (cdr ie) #f)))))))
+	      (define (make-sub-tables ie nc p)
+		 (let* ((l (length ie))
+			(w (/ 100. nc))
+			(iepc (let ((d (/ l nc)))
+				 (if (integer? d)
+				     (inexact->exact d)
+				     (+ 1 (inexact->exact (truncate d))))))
+			(split (list-split ie iepc)))
+		    (tr (map (lambda (ies)
+				(td :valign 'top :width w
+				   (if (pair? ies)
+				       (table :width 100. (make-column ies p))
+				       "")))
+			     split))))
+	      (let* ((ie (markup-body n))
+		     (nc (markup-option n :column))
+		     (loc (ast-loc n))
+		     (pref (eq? (engine-custom e 'index-page-ref) #t))
+		     (t (cond
+			   ((null? ie)
+			    "")
+			   ;; FIXME: Since we don't support
+			   ;; `:&skribe-eval-location', we could set up a
+			   ;; `parameterize' thing around `skribe-eval' to
+			   ;; provide it with the right location information.
+			   ((or (not (integer? nc)) (= nc 1))
+			    (table :width 100.
+			       ;;:&skribe-eval-location loc
+			       :class "index-table"
+			       (make-column ie pref)))
+			   (else
+			    (table :width 100.
+			       ;;:&skribe-eval-location loc
+			       :class "index-table"
+			       (make-sub-tables ie nc pref))))))
+		 (output (skribe-eval t e) e))))
+
+;*---------------------------------------------------------------------*/
+;*    &the-index-header ...                                            */
+;*    -------------------------------------------------------------    */
+;*    The index header is only useful for targets that support         */
+;*    hyperlinks such as HTML.                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-index-header
+   :action (lambda (n e) #f))
+
+;*---------------------------------------------------------------------*/
+;*    &prog-line ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&prog-line
+   :before (lambda (n e)
+	      (let ((n (markup-ident n)))
+		 (if n (skribe-eval (it (list n) ": ") e))))
+   :after "\n")
+
+;*---------------------------------------------------------------------*/
+;*    line-ref ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'line-ref
+   :options '(:offset)
+   :action (lambda (n e)
+	      (let ((o (markup-option n :offset))
+		    (n (markup-ident (handle-body (markup-body n)))))
+		 (skribe-eval (it (if (integer? o) (+ o n) n)) e))))
+
+
+
+;;;; A VIRER (mais handle-body n'est pas défini)
+(markup-writer 'line-ref
+   :options '(:offset)
+   :action #f)
diff --git a/src/guile/skribilo/engine/context.scm b/src/guile/skribilo/engine/context.scm
new file mode 100644
index 0000000..c9e0986
--- /dev/null
+++ b/src/guile/skribilo/engine/context.scm
@@ -0,0 +1,1382 @@
+;;;;
+;;;; context.skr	-- ConTeXt mode for Skribe
+;;;;
+;;;; Copyright © 2004 Erick Gallesio - I3S-CNRS/ESSI <eg@essi.fr>
+;;;;
+;;;;
+;;;; This program is free software; you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation; either version 2 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;;           Author: Erick Gallesio [eg@essi.fr]
+;;;;    Creation date: 23-Sep-2004 17:21 (eg)
+;;;; Last file update:  3-Nov-2004 12:54 (eg)
+;;;;
+
+(define-skribe-module (skribilo engine context))
+
+;;;; ======================================================================
+;;;;	context-customs ...
+;;;; ======================================================================
+(define context-customs
+  '((source-comment-color "#ffa600")
+    (source-error-color "red")
+    (source-define-color "#6959cf")
+    (source-module-color "#1919af")
+    (source-markup-color "#1919af")
+    (source-thread-color "#ad4386")
+    (source-string-color "red")
+    (source-bracket-color "red")
+    (source-type-color "#00cf00")
+    (index-page-ref #t)
+    (image-format ("jpg"))
+    (font-size 11)
+    (font-type "roman")
+    (user-style #f)
+    (document-style "book")))
+
+;;;; ======================================================================
+;;;;	context-encoding ...
+;;;; ======================================================================
+(define context-encoding
+  '((#\# "\\type{#}")
+    (#\| "\\type{|}")
+    (#\{ "$\\{$")
+    (#\} "$\\}$")
+    (#\~ "\\type{~}")
+    (#\& "\\type{&}")
+    (#\_ "\\type{_}")
+    (#\^ "\\type{^}")
+    (#\[ "\\type{[}")
+    (#\] "\\type{]}")
+    (#\< "\\type{<}")
+    (#\> "\\type{>}")
+    (#\$ "\\type{$}")
+    (#\% "\\%")
+    (#\\ "$\\backslash$")))
+
+;;;; ======================================================================
+;;;;	context-pre-encoding ...
+;;;; ======================================================================
+(define context-pre-encoding
+  (append '((#\space "~")
+	    (#\~ "\\type{~}"))
+	  context-encoding))
+
+
+;;;; ======================================================================
+;;;;	context-symbol-table ...
+;;;; ======================================================================
+(define (context-symbol-table math)
+   `(("iexcl" "!`")
+     ("cent" "c")
+     ("pound" "\\pounds")
+     ("yen" "Y")
+     ("section" "\\S")
+     ("mul" ,(math "^-"))
+     ("copyright" "\\copyright")
+     ("lguillemet" ,(math "\\ll"))
+     ("not" ,(math "\\neg"))
+     ("degree" ,(math "^{\\small{o}}"))
+     ("plusminus" ,(math "\\pm"))
+     ("micro" ,(math "\\mu"))
+     ("paragraph" "\\P")
+     ("middot" ,(math "\\cdot"))
+     ("rguillemet" ,(math "\\gg"))
+     ("1/4" ,(math "\\frac{1}{4}"))
+     ("1/2" ,(math "\\frac{1}{2}"))
+     ("3/4" ,(math "\\frac{3}{4}"))
+     ("iquestion" "?`")
+     ("Agrave" "\\`{A}")
+     ("Aacute" "\\'{A}")
+     ("Acircumflex" "\\^{A}")
+     ("Atilde" "\\~{A}")
+     ("Amul" "\\\"{A}")
+     ("Aring" "{\\AA}")
+     ("AEligature" "{\\AE}")
+     ("Oeligature" "{\\OE}")
+     ("Ccedilla" "{\\c{C}}")
+     ("Egrave" "{\\`{E}}")
+     ("Eacute" "{\\'{E}}")
+     ("Ecircumflex" "{\\^{E}}")
+     ("Euml" "\\\"{E}")
+     ("Igrave" "{\\`{I}}")
+     ("Iacute" "{\\'{I}}")
+     ("Icircumflex" "{\\^{I}}")
+     ("Iuml" "\\\"{I}")
+     ("ETH" "D")
+     ("Ntilde" "\\~{N}")
+     ("Ograve" "\\`{O}")
+     ("Oacute" "\\'{O}")
+     ("Ocurcumflex" "\\^{O}")
+     ("Otilde" "\\~{O}")
+     ("Ouml" "\\\"{O}")
+     ("times" ,(math "\\times"))
+     ("Oslash" "\\O")
+     ("Ugrave" "\\`{U}")
+     ("Uacute" "\\'{U}")
+     ("Ucircumflex" "\\^{U}")
+     ("Uuml" "\\\"{U}")
+     ("Yacute" "\\'{Y}")
+     ("szlig" "\\ss")
+     ("agrave" "\\`{a}")
+     ("aacute" "\\'{a}")
+     ("acircumflex" "\\^{a}")
+     ("atilde" "\\~{a}")
+     ("amul" "\\\"{a}")
+     ("aring" "\\aa")
+     ("aeligature" "\\ae")
+     ("oeligature" "{\\oe}")
+     ("ccedilla" "{\\c{c}}")
+     ("egrave" "{\\`{e}}")
+     ("eacute" "{\\'{e}}")
+     ("ecircumflex" "{\\^{e}}")
+     ("euml" "\\\"{e}")
+     ("igrave" "{\\`{\\i}}")
+     ("iacute" "{\\'{\\i}}")
+     ("icircumflex" "{\\^{\\i}}")
+     ("iuml" "\\\"{\\i}")
+     ("ntilde" "\\~{n}")
+     ("ograve" "\\`{o}")
+     ("oacute" "\\'{o}")
+     ("ocurcumflex" "\\^{o}")
+     ("otilde" "\\~{o}")
+     ("ouml" "\\\"{o}")
+     ("divide" ,(math "\\div"))
+     ("oslash" "\\o")
+     ("ugrave" "\\`{u}")
+     ("uacute" "\\'{u}")
+     ("ucircumflex" "\\^{u}")
+     ("uuml" "\\\"{u}")
+     ("yacute" "\\'{y}")
+     ("ymul" "\\\"{y}")
+     ;; Greek
+     ("Alpha" "A")
+     ("Beta" "B")
+     ("Gamma" ,(math "\\Gamma"))
+     ("Delta" ,(math "\\Delta"))
+     ("Epsilon" "E")
+     ("Zeta" "Z")
+     ("Eta" "H")
+     ("Theta" ,(math "\\Theta"))
+     ("Iota" "I")
+     ("Kappa" "K")
+     ("Lambda" ,(math "\\Lambda"))
+     ("Mu" "M")
+     ("Nu" "N")
+     ("Xi" ,(math "\\Xi"))
+     ("Omicron" "O")
+     ("Pi" ,(math "\\Pi"))
+     ("Rho" "P")
+     ("Sigma" ,(math "\\Sigma"))
+     ("Tau" "T")
+     ("Upsilon" ,(math "\\Upsilon"))
+     ("Phi" ,(math "\\Phi"))
+     ("Chi" "X")
+     ("Psi" ,(math "\\Psi"))
+     ("Omega" ,(math "\\Omega"))
+     ("alpha" ,(math "\\alpha"))
+     ("beta" ,(math "\\beta"))
+     ("gamma" ,(math "\\gamma"))
+     ("delta" ,(math "\\delta"))
+     ("epsilon" ,(math "\\varepsilon"))
+     ("zeta" ,(math "\\zeta"))
+     ("eta" ,(math "\\eta"))
+     ("theta" ,(math "\\theta"))
+     ("iota" ,(math "\\iota"))
+     ("kappa" ,(math "\\kappa"))
+     ("lambda" ,(math "\\lambda"))
+     ("mu" ,(math "\\mu"))
+     ("nu" ,(math "\\nu"))
+     ("xi" ,(math "\\xi"))
+     ("omicron" ,(math "\\o"))
+     ("pi" ,(math "\\pi"))
+     ("rho" ,(math "\\rho"))
+     ("sigmaf" ,(math "\\varsigma"))
+     ("sigma" ,(math "\\sigma"))
+     ("tau" ,(math "\\tau"))
+     ("upsilon" ,(math "\\upsilon"))
+     ("phi" ,(math "\\varphi"))
+     ("chi" ,(math "\\chi"))
+     ("psi" ,(math "\\psi"))
+     ("omega" ,(math "\\omega"))
+     ("thetasym" ,(math "\\vartheta"))
+     ("piv" ,(math "\\varpi"))
+     ;; punctuation
+     ("bullet" ,(math "\\bullet"))
+     ("ellipsis" ,(math "\\ldots"))
+     ("weierp" ,(math "\\wp"))
+     ("image" ,(math "\\Im"))
+     ("real" ,(math "\\Re"))
+     ("tm" ,(math "^{\\sc\\tiny{tm}}"))
+     ("alef" ,(math "\\aleph"))
+     ("<-" ,(math "\\leftarrow"))
+     ("<--" ,(math "\\longleftarrow"))
+     ("uparrow" ,(math "\\uparrow"))
+     ("->" ,(math "\\rightarrow"))
+     ("-->" ,(math "\\longrightarrow"))
+     ("downarrow" ,(math "\\downarrow"))
+     ("<->" ,(math "\\leftrightarrow"))
+     ("<-->" ,(math "\\longleftrightarrow"))
+     ("<+" ,(math "\\hookleftarrow"))
+     ("<=" ,(math "\\Leftarrow"))
+     ("<==" ,(math "\\Longleftarrow"))
+     ("Uparrow" ,(math "\\Uparrow"))
+     ("=>" ,(math "\\Rightarrow"))
+     ("==>" ,(math "\\Longrightarrow"))
+     ("Downarrow" ,(math "\\Downarrow"))
+     ("<=>" ,(math "\\Leftrightarrow"))
+     ("<==>" ,(math "\\Longleftrightarrow"))
+     ;; Mathematical operators
+     ("forall" ,(math "\\forall"))
+     ("partial" ,(math "\\partial"))
+     ("exists" ,(math "\\exists"))
+     ("emptyset" ,(math "\\emptyset"))
+     ("infinity" ,(math "\\infty"))
+     ("nabla" ,(math "\\nabla"))
+     ("in" ,(math "\\in"))
+     ("notin" ,(math "\\notin"))
+     ("ni" ,(math "\\ni"))
+     ("prod" ,(math "\\Pi"))
+     ("sum" ,(math "\\Sigma"))
+     ("asterisk" ,(math "\\ast"))
+     ("sqrt" ,(math "\\surd"))
+     ("propto" ,(math "\\propto"))
+     ("angle" ,(math "\\angle"))
+     ("and" ,(math "\\wedge"))
+     ("or" ,(math "\\vee"))
+     ("cap" ,(math "\\cap"))
+     ("cup" ,(math "\\cup"))
+     ("integral" ,(math "\\int"))
+     ("models" ,(math "\\models"))
+     ("vdash" ,(math "\\vdash"))
+     ("dashv" ,(math "\\dashv"))
+     ("sim" ,(math "\\sim"))
+     ("cong" ,(math "\\cong"))
+     ("approx" ,(math "\\approx"))
+     ("neq" ,(math "\\neq"))
+     ("equiv" ,(math "\\equiv"))
+     ("le" ,(math "\\leq"))
+     ("ge" ,(math "\\geq"))
+     ("subset" ,(math "\\subset"))
+     ("supset" ,(math "\\supset"))
+     ("subseteq" ,(math "\\subseteq"))
+     ("supseteq" ,(math "\\supseteq"))
+     ("oplus" ,(math "\\oplus"))
+     ("otimes" ,(math "\\otimes"))
+     ("perp" ,(math "\\perp"))
+     ("mid" ,(math "\\mid"))
+     ("lceil" ,(math "\\lceil"))
+     ("rceil" ,(math "\\rceil"))
+     ("lfloor" ,(math "\\lfloor"))
+     ("rfloor" ,(math "\\rfloor"))
+     ("langle" ,(math "\\langle"))
+     ("rangle" ,(math "\\rangle"))
+     ;; Misc
+     ("loz" ,(math "\\diamond"))
+     ("spades" ,(math "\\spadesuit"))
+     ("clubs" ,(math "\\clubsuit"))
+     ("hearts" ,(math "\\heartsuit"))
+     ("diams" ,(math "\\diamondsuit"))
+     ("euro" "\\euro{}")
+     ;; ConTeXt
+     ("dag" "\\dag")
+     ("ddag" "\\ddag")
+     ("circ" ,(math "\\circ"))
+     ("top" ,(math "\\top"))
+     ("bottom" ,(math "\\bot"))
+     ("lhd" ,(math "\\triangleleft"))
+     ("rhd" ,(math "\\triangleright"))
+     ("parallel" ,(math "\\parallel"))))
+
+;;;; ======================================================================
+;;;;	context-width
+;;;; ======================================================================
+(define (context-width width)
+  (cond
+    ((string? width)
+     width)
+    ((and (number? width) (inexact? width))
+     (string-append (number->string (/ width 100.)) "\\textwidth"))
+    (else
+     (string-append (number->string width) "pt"))))
+
+;;;; ======================================================================
+;;;;	context-dim
+;;;; ======================================================================
+(define (context-dim dimension)
+  (cond
+    ((string? dimension)
+     dimension)
+    ((number? dimension)
+     (string-append (number->string (inexact->exact (round dimension)))
+		    "pt"))))
+
+;;;; ======================================================================
+;;;;	context-url
+;;;; ======================================================================
+(define(context-url url text e)
+  (let ((name (gensym 'url))
+	(text (or text url)))
+    (printf "\\useURL[~A][~A][][" name url)
+    (output text e)
+    (printf "]\\from[~A]" name)))
+
+;;;; ======================================================================
+;;;;	Color Management ...
+;;;; ======================================================================
+(define *skribe-context-color-table* (make-hashtable))
+
+(define (skribe-color->context-color spec)
+  (receive (r g b)
+     (skribe-color->rgb spec)
+     (let ((ff (exact->inexact #xff)))
+       (format "r=~a,g=~a,b=~a"
+	       (number->string (/ r ff))
+	       (number->string (/ g ff))
+	       (number->string (/ b ff))))))
+
+
+(define (skribe-declare-used-colors)
+  (printf "\n%%Colors\n")
+  (for-each (lambda (spec)
+	      (let ((c (hashtable-get *skribe-context-color-table* spec)))
+		(unless (string? c)
+		  ;; Color was never used before
+		  (let ((name (symbol->string (gensym 'col))))
+		    (hashtable-put! *skribe-context-color-table* spec name)
+		    (printf "\\definecolor[~A][~A]\n"
+			    name
+			    (skribe-color->context-color spec))))))
+	    (skribe-get-used-colors))
+  (newline))
+
+(define (skribe-declare-standard-colors engine)
+  (for-each (lambda (x)
+	      (skribe-use-color! (engine-custom engine x)))
+	    '(source-comment-color source-define-color source-module-color
+	      source-markup-color  source-thread-color source-string-color
+	      source-bracket-color source-type-color)))
+
+(define (skribe-get-color spec)
+  (let ((c (and (hashtable? *skribe-context-color-table*)
+		(hashtable-get *skribe-context-color-table* spec))))
+    (if (not (string? c))
+	(skribe-error 'context "Can't find color" spec)
+	c)))
+
+;;;; ======================================================================
+;;;;	context-engine ...
+;;;; ======================================================================
+(define context-engine
+   (default-engine-set!
+      (make-engine 'context
+	 :version 1.0
+	 :format "context"
+	 :delegate (find-engine 'base)
+	 :filter (make-string-replace context-encoding)
+	 :symbol-table (context-symbol-table (lambda (m) (format #f "$~a$" m)))
+	 :custom context-customs)))
+
+;;;; ======================================================================
+;;;;	document ...
+;;;; ======================================================================
+(markup-writer 'document
+   :options '(:title :subtitle :author :ending :env)
+   :before (lambda (n e)
+	     ;; Prelude
+	     (printf "% interface=en output=pdftex\n")
+	     (display "%%%% -*- TeX -*-\n")
+	     (printf "%%%% File automatically generated by Skribe ~A on ~A\n\n"
+		     (skribe-release) (date))
+	     ;; Make URLs active
+	     (printf "\\setupinteraction[state=start]\n")
+	     ;; Choose the document font
+	     (printf "\\setupbodyfont[~a,~apt]\n" (engine-custom e 'font-type)
+		     (engine-custom e 'font-size))
+	     ;; Color
+	     (display "\\setupcolors[state=start]\n")
+	     ;; Load Style
+	     (printf "\\input skribe-context-~a.tex\n"
+		     (engine-custom e 'document-style))
+	     ;; Insert User customization
+	     (let ((s (engine-custom e 'user-style)))
+	       (when s (printf "\\input ~a\n" s)))
+	     ;; Output used colors
+	     (skribe-declare-standard-colors e)
+	     (skribe-declare-used-colors)
+
+	     (display "\\starttext\n\\StartTitlePage\n")
+	     ;; title
+	     (let ((t (markup-option n :title)))
+	       (when t
+		 (skribe-eval (new markup
+				   (markup '&context-title)
+				   (body t)
+				   (options
+				      `((subtitle ,(markup-option n :subtitle)))))
+			      e
+			      :env `((parent ,n)))))
+	     ;; author(s)
+	     (let ((a (markup-option n :author)))
+	       (when a
+		 (if (list? a)
+		     ;; List of authors. Use multi-columns
+		     (begin
+		       (printf "\\defineparagraphs[Authors][n=~A]\n" (length a))
+		       (display "\\startAuthors\n")
+		       (let Loop ((l a))
+			 (unless (null? l)
+			   (output (car l) e)
+			   (unless (null? (cdr l))
+			     (display "\\nextAuthors\n")
+			     (Loop (cdr l)))))
+		       (display "\\stopAuthors\n\n"))
+		     ;; One author, that's easy
+		     (output a e))))
+	     ;; End of the title
+	     (display "\\StopTitlePage\n"))
+   :after (lambda (n e)
+	     (display "\n\\stoptext\n")))
+
+
+
+;;;; ======================================================================
+;;;;	&context-title ...
+;;;; ======================================================================
+(markup-writer '&context-title
+   :before "{\\DocumentTitle{"
+   :action (lambda (n e)
+	     (output (markup-body n) e)
+	     (let ((sub (markup-option n 'subtitle)))
+	       (when sub
+		 (display "\\\\\n\\switchtobodyfont[16pt]\\it{")
+		 (output sub e)
+		 (display "}\n"))))
+   :after "}}")
+
+;;;; ======================================================================
+;;;;	author ...
+;;;; ======================================================================
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address :phone :photo :align)
+   :action (lambda (n e)
+	     (let ((name        (markup-option n :name))
+		   (title       (markup-option n :title))
+		   (affiliation (markup-option n :affiliation))
+		   (email       (markup-option n :email))
+		   (url         (markup-option n :url))
+		   (address     (markup-option n :address))
+		   (phone       (markup-option n :phone))
+		   (out         (lambda (n)
+				  (output n e)
+				  (display "\\\\\n"))))
+	       (display "{\\midaligned{")
+	       (when name	(out name))
+	       (when title	(out title))
+	       (when affiliation	(out affiliation))
+	       (when (pair? address)	(for-each out address))
+	       (when phone		(out phone))
+	       (when email		(out email))
+	       (when url		(out url))
+	       (display "}}\n"))))
+
+
+;;;; ======================================================================
+;;;;	toc ...
+;;;; ======================================================================
+(markup-writer 'toc
+   :options '()
+   :action (lambda (n e) (display "\\placecontent\n")))
+
+;;;; ======================================================================
+;;;;	context-block-before ...
+;;;; ======================================================================
+(define (context-block-before name name-unnum)
+   (lambda (n e)
+      (let ((num (markup-option n :number)))
+	 (printf "\n\n%% ~a\n" (string-canonicalize (markup-ident n)))
+	 (printf "\\~a[~a]{" (if num name name-unnum)
+		 (string-canonicalize (markup-ident n)))
+	 (output (markup-option n :title) e)
+	 (display "}\n"))))
+
+
+;;;; ======================================================================
+;;;;	chapter, section,  ...
+;;;; ======================================================================
+(markup-writer 'chapter
+   :options '(:title :number :toc :file :env)
+   :before (context-block-before 'chapter 'title))
+
+
+(markup-writer 'section
+   :options '(:title :number :toc :file :env)
+   :before (context-block-before 'section 'subject))
+
+
+(markup-writer 'subsection
+   :options '(:title :number :toc :file :env)
+   :before (context-block-before 'subsection 'subsubject))
+
+
+(markup-writer 'subsubsection
+   :options '(:title :number :toc :file :env)
+   :before (context-block-before 'subsubsection 'subsubsubject))
+
+;;;; ======================================================================
+;;;;    paragraph ...
+;;;; ======================================================================
+(markup-writer 'paragraph
+   :options '(:title :number :toc :env)
+   :after "\\par\n")
+
+;;;; ======================================================================
+;;;;	footnote ...
+;;;; ======================================================================
+(markup-writer 'footnote
+   :before "\\footnote{"
+   :after "}")
+
+;;;; ======================================================================
+;;;;	linebreak ...
+;;;; ======================================================================
+(markup-writer 'linebreak
+   :action "\\crlf ")
+
+;;;; ======================================================================
+;;;;	hrule ...
+;;;; ======================================================================
+(markup-writer 'hrule
+   :options '(:width :height)
+   :before (lambda (n e)
+	     (printf "\\blackrule[width=~A,height=~A]\n"
+		     (context-width  (markup-option n :width))
+		     (context-dim    (markup-option n :height)))))
+
+;;;; ======================================================================
+;;;;	color ...
+;;;; ======================================================================
+(markup-writer 'color
+   :options '(:bg :fg :width :margin :border)
+   :before (lambda (n e)
+	     (let ((bg (markup-option n :bg))
+		   (fg (markup-option n :fg))
+		   (w  (markup-option n :width))
+		   (m  (markup-option n :margin))
+		   (b  (markup-option n :border))
+		   (c  (markup-option n :round-corner)))
+	       (if (or bg w m b)
+		   (begin
+		     (printf "\\startframedtext[width=~a" (if w
+							      (context-width w)
+							      "fit"))
+		     (printf ",rulethickness=~A" (if b (context-width b) "0pt"))
+		     (when m
+		       (printf ",offset=~A" (context-width m)))
+		     (when bg
+		       (printf ",background=color,backgroundcolor=~A"
+			       (skribe-get-color bg)))
+		     (when fg
+		       (printf ",foregroundcolor=~A"
+			       (skribe-get-color fg)))
+		     (when c
+		       (display ",framecorner=round"))
+		     (printf "]\n"))
+		   ;; Probably just a foreground was specified
+		   (when fg
+		     (printf "\\startcolor[~A] " (skribe-get-color fg))))))
+   :after (lambda (n e)
+	    (let ((bg (markup-option n :bg))
+		   (fg (markup-option n :fg))
+		   (w  (markup-option n :width))
+		   (m  (markup-option n :margin))
+		   (b  (markup-option n :border)))
+	      (if (or bg w m b)
+		(printf "\\stopframedtext ")
+		(when fg
+		  (printf "\\stopcolor "))))))
+;;;; ======================================================================
+;;;;	frame ...
+;;;; ======================================================================
+(markup-writer 'frame
+   :options '(:width :border :margin)
+   :before (lambda (n e)
+	     (let ((m (markup-option n :margin))
+		   (w (markup-option n :width))
+		   (b (markup-option n :border))
+		   (c (markup-option n :round-corner)))
+	       (printf "\\startframedtext[width=~a" (if w
+							(context-width w)
+							"fit"))
+	       (printf ",rulethickness=~A" (context-dim b))
+	       (printf ",offset=~A" (context-width m))
+	       (when c
+		 (display ",framecorner=round"))
+	       (printf "]\n")))
+   :after "\\stopframedtext ")
+
+;;;; ======================================================================
+;;;;	font ...
+;;;; ======================================================================
+(markup-writer 'font
+   :options '(:size)
+   :action (lambda (n e)
+	     (let* ((size (markup-option n :size))
+		    (cs   (engine-custom e 'font-size))
+		    (ns   (cond
+			    ((and (integer? size) (exact? size))
+			     (if (> size 0)
+				 size
+				 (+ cs size)))
+			    ((and (number? size) (inexact? size))
+			     (+ cs (inexact->exact size)))
+			    ((string? size)
+			     (let ((nb (string->number size)))
+			       (if (not (number? nb))
+				   (skribe-error
+				    'font
+				    (format #f "Illegal font size ~s" size)
+				    nb)
+				   (+ cs nb))))))
+		     (ne (make-engine (gensym 'context)
+				      :delegate e
+				      :filter (engine-filter e)
+				      :symbol-table (engine-symbol-table e)
+				      :custom `((font-size ,ns)
+						,@(engine-customs e)))))
+	       (printf "{\\switchtobodyfont[~apt]" ns)
+	       (output (markup-body n) ne)
+	       (display "}"))))
+
+
+;;;; ======================================================================
+;;;;    flush ...
+;;;; ======================================================================
+(markup-writer 'flush
+   :options '(:side)
+   :before (lambda (n e)
+	     (case (markup-option n :side)
+		 ((center)
+		  (display "\n\n\\midaligned{"))
+		 ((left)
+		  (display "\n\n\\leftaligned{"))
+		 ((right)
+		  (display "\n\n\\rightaligned{"))))
+   :after "}\n")
+
+;*---------------------------------------------------------------------*/
+;*    center ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'center
+   :before "\n\n\\midaligned{"
+   :after "}\n")
+
+;;;; ======================================================================
+;;;;   pre ...
+;;;; ======================================================================
+(markup-writer 'pre
+   :before "{\\tt\n\\startlines\n\\fixedspaces\n"
+   :action (lambda (n e)
+	     (let ((ne (make-engine
+			  (gensym 'context)
+			  :delegate e
+			  :filter (make-string-replace context-pre-encoding)
+			  :symbol-table (engine-symbol-table e)
+			  :custom (engine-customs e))))
+	       (output (markup-body n) ne)))
+   :after  "\n\\stoplines\n}")
+
+;;;; ======================================================================
+;;;;	prog ...
+;;;; ======================================================================
+(markup-writer 'prog
+   :options '(:line :mark)
+   :before "{\\tt\n\\startlines\n\\fixedspaces\n"
+   :action (lambda (n e)
+	     (let ((ne (make-engine
+			  (gensym 'context)
+			  :delegate e
+			  :filter (make-string-replace context-pre-encoding)
+			  :symbol-table (engine-symbol-table e)
+			  :custom (engine-customs e))))
+	       (output (markup-body n) ne)))
+   :after  "\n\\stoplines\n}")
+
+
+;;;; ======================================================================
+;;;;    itemize, enumerate ...
+;;;; ======================================================================
+(define (context-itemization-action n e descr?)
+  (let ((symbol (markup-option n :symbol)))
+    (for-each (lambda (item)
+		(if symbol
+		    (begin
+		      (display "\\sym{")
+		      (output symbol e)
+		      (display "}"))
+		    ;; output a \item iff not a description
+		    (unless descr?
+		      (display "  \\item ")))
+		(output item e)
+		(newline))
+	      (markup-body n))))
+
+(markup-writer 'itemize
+   :options '(:symbol)
+   :before "\\startnarrower[left]\n\\startitemize[serried]\n"
+   :action (lambda (n e) (context-itemization-action n e #f))
+   :after "\\stopitemize\n\\stopnarrower\n")
+
+
+(markup-writer 'enumerate
+   :options '(:symbol)
+   :before "\\startnarrower[left]\n\\startitemize[n][standard]\n"
+   :action (lambda (n e) (context-itemization-action n e #f))
+   :after "\\stopitemize\n\\stopnarrower\n")
+
+;;;; ======================================================================
+;;;;    description ...
+;;;; ======================================================================
+(markup-writer 'description
+   :options '(:symbol)
+   :before "\\startnarrower[left]\n\\startitemize[serried]\n"
+   :action (lambda (n e) (context-itemization-action n e #t))
+   :after "\\stopitemize\n\\stopnarrower\n")
+
+;;;; ======================================================================
+;;;;    item ...
+;;;; ======================================================================
+(markup-writer 'item
+   :options '(:key)
+   :action (lambda (n e)
+	     (let ((k (markup-option n :key)))
+	       (when k
+		 ;; Output the key(s)
+		 (let Loop ((l (if (pair? k) k (list k))))
+		   (unless (null? l)
+		     (output (bold (car l)) e)
+		     (unless (null? (cdr l))
+		       (display "\\crlf\n"))
+		     (Loop (cdr l))))
+		 (display "\\nowhitespace\\startnarrower[left]\n"))
+	       ;; Output body
+	       (output (markup-body n) e)
+	       ;; Terminate
+	       (when k
+		 (display "\n\\stopnarrower\n")))))
+
+;;;; ======================================================================
+;;;;	blockquote ...
+;;;; ======================================================================
+(markup-writer 'blockquote
+   :before "\n\\startnarrower[left,right]\n"
+   :after  "\n\\stopnarrower\n")
+
+
+;;;; ======================================================================
+;;;;	figure ...
+;;;; ======================================================================
+(markup-writer 'figure
+   :options '(:legend :number :multicolumns)
+   :action (lambda (n e)
+	     (let ((ident (markup-ident n))
+		   (number (markup-option n :number))
+		   (legend (markup-option n :legend)))
+	       (unless number
+		 (display "{\\setupcaptions[number=off]\n"))
+	       (display "\\placefigure\n")
+	       (printf "  [~a]\n" (string-canonicalize ident))
+	       (display "  {") (output legend e) (display "}\n")
+	       (display "  {") (output (markup-body n) e) (display "}")
+	       (unless number
+		 (display "}\n")))))
+
+;;;; ======================================================================
+;;;;    table ...
+;;;; ======================================================================
+						;; width doesn't work
+(markup-writer 'table
+   :options '(:width :border :frame :rules :cellpadding)
+   :before (lambda (n e)
+	     (let ((width  (markup-option n :width))
+		   (border (markup-option n :border))
+		   (frame  (markup-option n :frame))
+		   (rules  (markup-option n :rules))
+		   (cstyle (markup-option n :cellstyle))
+		   (cp     (markup-option n :cellpadding))
+		   (cs     (markup-option n :cellspacing)))
+	       (printf "\n{\\bTABLE\n")
+	       (printf "\\setupTABLE[")
+	       (printf "width=~A" (if width (context-width width) "fit"))
+	       (when border
+		 (printf ",rulethickness=~A" (context-dim border)))
+	       (when cp
+		 (printf ",offset=~A" (context-width cp)))
+	       (printf ",frame=off]\n")
+
+	       (when rules
+		 (let ((hor  "\\setupTABLE[row][bottomframe=on,topframe=on]\n")
+		       (vert "\\setupTABLE[c][leftframe=on,rightframe=on]\n"))
+		   (case rules
+		     ((rows) (display hor))
+		     ((cols) (display vert))
+		     ((all)  (display hor) (display vert)))))
+
+	       (when frame
+		 ;;  hsides, vsides, lhs, rhs, box, border
+		 (let ((top   "\\setupTABLE[row][first][frame=off,topframe=on]\n")
+		       (bot   "\\setupTABLE[row][last][frame=off,bottomframe=on]\n")
+		       (left  "\\setupTABLE[c][first][frame=off,leftframe=on]\n")
+		       (right "\\setupTABLE[c][last][frame=off,rightframe=on]\n"))
+		 (case frame
+		   ((above)      (display top))
+		   ((below)      (display bot))
+		   ((hsides)     (display top) (display bot))
+		   ((lhs)        (display left))
+		   ((rhs)        (display right))
+		   ((vsides)     (display left) (diplay right))
+		   ((box border) (display top)  (display bot)
+				 (display left) (display right)))))))
+
+   :after  (lambda (n e)
+	     (printf "\\eTABLE}\n")))
+
+
+;;;; ======================================================================
+;;;;    tr ...
+;;;; ======================================================================
+(markup-writer 'tr
+   :options '(:bg)
+   :before (lambda (n e)
+	     (display "\\bTR")
+	     (let ((bg (markup-option n :bg)))
+	       (when bg
+		 (printf "[background=color,backgroundcolor=~A]"
+			 (skribe-get-color bg)))))
+   :after  "\\eTR\n")
+
+
+;;;; ======================================================================
+;;;;    tc ...
+;;;; ======================================================================
+(markup-writer 'tc
+   :options '(:width :align :valign :colspan)
+   :before (lambda (n e)
+	     (let ((th?     (eq? 'th (markup-option n 'markup)))
+		   (width   (markup-option n :width))
+		   (align   (markup-option n :align))
+		   (valign  (markup-option n :valign))
+		   (colspan (markup-option n :colspan))
+		   (rowspan (markup-option n :rowspan))
+		   (bg      (markup-option n :bg)))
+	       (printf "\\bTD[")
+	       (printf "width=~a" (if width (context-width width) "fit"))
+	       (when valign
+		 ;; This is buggy. In fact valign an align can't be both
+		 ;; specified in ConTeXt
+		 (printf ",align=~a" (case valign
+				       ((center) 'lohi)
+				       ((bottom) 'low)
+				       ((top)    'high))))
+	       (when align
+		 (printf ",align=~a" (case align
+				       ((left) 'right) ; !!!!
+				       ((right) 'left) ; !!!!
+				       (else    'middle))))
+	       (unless (equal? colspan 1)
+		 (printf ",nx=~a" colspan))
+	       (display "]")
+	       (when th?
+		 ;; This is a TH, output is bolded
+		 (display "{\\bf{"))))
+
+   :after (lambda (n e)
+	     (when (equal? (markup-option n 'markup) 'th)
+	       ;; This is a TH, output is bolded
+	       (display "}}"))
+	     (display "\\eTD")))
+
+;;;; ======================================================================
+;;;;	image ...
+;;;; ======================================================================
+(markup-writer 'image
+   :options '(:file :url :width :height :zoom)
+   :action (lambda (n e)
+	     (let* ((file   (markup-option n :file))
+		    (url    (markup-option n :url))
+		    (width  (markup-option n :width))
+		    (height (markup-option n :height))
+		    (zoom   (markup-option n :zoom))
+		    (body   (markup-body n))
+		    (efmt   (engine-custom e 'image-format))
+		    (img    (or url (convert-image file
+						   (if (list? efmt)
+						       efmt
+						       '("jpg"))))))
+	       (if (not (string? img))
+		   (skribe-error 'context "Illegal image" file)
+		   (begin
+		     (printf "\\externalfigure[~A][frame=off" (strip-ref-base img))
+		     (if zoom   (printf ",factor=~a"   (inexact->exact zoom)))
+		     (if width  (printf ",width=~a"    (context-width width)))
+		     (if height (printf ",height=~apt" (context-dim height)))
+		     (display "]"))))))
+
+
+;;;; ======================================================================
+;;;;   Ornaments ...
+;;;; ======================================================================
+(markup-writer 'roman :before "{\\rm{" :after "}}")
+(markup-writer 'bold :before "{\\bf{" :after "}}")
+(markup-writer 'underline :before  "{\\underbar{" :after "}}")
+(markup-writer 'emph :before "{\\em{" :after "}}")
+(markup-writer 'it :before "{\\it{" :after "}}")
+(markup-writer 'code :before "{\\tt{" :after "}}")
+(markup-writer 'var :before "{\\tt{" :after "}}")
+(markup-writer 'sc :before "{\\sc{" :after "}}")
+;;//(markup-writer 'sf :before "{\\sf{" :after "}}")
+(markup-writer 'sub :before "{\\low{" :after "}}")
+(markup-writer 'sup :before "{\\high{" :after "}}")
+
+
+;;//
+;;//(markup-writer 'tt
+;;//   :before "{\\texttt{"
+;;//   :action (lambda (n e)
+;;//	      (let ((ne (make-engine
+;;//			   (gensym 'latex)
+;;//			   :delegate e
+;;//			   :filter (make-string-replace latex-tt-encoding)
+;;//			   :custom (engine-customs e)
+;;//			   :symbol-table (engine-symbol-table e))))
+;;//		 (output (markup-body n) ne)))
+;;//   :after "}}")
+
+;;;; ======================================================================
+;;;;    q ...
+;;;; ======================================================================
+(markup-writer 'q
+   :before "\\quotation{"
+   :after "}")
+
+;;;; ======================================================================
+;;;;    mailto ...
+;;;; ======================================================================
+(markup-writer 'mailto
+   :options '(:text)
+   :action (lambda (n e)
+	     (let ((text (markup-option n :text))
+		   (url  (markup-body n)))
+	       (when (pair? url)
+		 (context-url (format #f "mailto:~A" (car url))
+			      (or text
+				  (car url))
+			      e)))))
+;;;; ======================================================================
+;;;;   mark ...
+;;;; ======================================================================
+(markup-writer 'mark
+   :before (lambda (n e)
+	      (printf "\\reference[~a]{}\n"
+		      (string-canonicalize (markup-ident n)))))
+
+;;;; ======================================================================
+;;;;   ref ...
+;;;; ======================================================================
+(markup-writer 'ref
+   :options '(:text :chapter :section :subsection :subsubsection
+	      :figure :mark :handle :page)
+   :action (lambda (n e)
+	      (let* ((text (markup-option n :text))
+		     (page (markup-option n :page))
+		     (c    (handle-ast (markup-body n)))
+		     (id   (markup-ident c)))
+		(cond
+		  (page ;; Output the page only (this is a hack)
+		     (when text (output text e))
+		     (printf "\\at[~a]"
+			     (string-canonicalize id)))
+		  ((or (markup-option n :chapter)
+		       (markup-option n :section)
+		       (markup-option n :subsection)
+		       (markup-option n :subsubsection))
+		   (if text
+		       (printf "\\goto{~a}[~a]" (or text id)
+			       (string-canonicalize id))
+		       (printf "\\in[~a]" (string-canonicalize id))))
+		  ((markup-option n :mark)
+		     (printf "\\goto{~a}[~a]"
+			     (or text id)
+			     (string-canonicalize id)))
+		  (else ;; Output a little image indicating the direction
+		      (printf "\\in[~a]" (string-canonicalize id)))))))
+
+;;;; ======================================================================
+;;;;   bib-ref ...
+;;;; ======================================================================
+(markup-writer 'bib-ref
+   :options '(:text :bib)
+   :before (lambda (n e) (output "[" e))
+   :action (lambda (n e)
+	     (let* ((obj   (handle-ast (markup-body n)))
+		    (title (markup-option obj :title))
+		    (ref   (markup-option title 'number))
+		    (ident (markup-ident obj)))
+	       (printf "\\goto{~a}[~a]" ref (string-canonicalize ident))))
+   :after (lambda (n e) (output "]" e)))
+
+;;;; ======================================================================
+;;;;   bib-ref+ ...
+;;;; ======================================================================
+(markup-writer 'bib-ref+
+   :options '(:text :bib)
+   :before (lambda (n e) (output "[" e))
+   :action (lambda (n e)
+	      (let loop ((rs (markup-body n)))
+		 (cond
+		    ((null? rs)
+		     #f)
+		    (else
+		     (if (is-markup? (car rs) 'bib-ref)
+			 (invoke (writer-action (markup-writer-get 'bib-ref e))
+				 (car rs)
+				 e)
+			 (output (car rs) e))
+		     (if (pair? (cdr rs))
+			 (begin
+			    (display ",")
+			    (loop (cdr rs))))))))
+   :after (lambda (n e) (output "]" e)))
+
+;;;; ======================================================================
+;;;;	url-ref ...
+;;;; ======================================================================
+(markup-writer 'url-ref
+   :options '(:url :text)
+   :action (lambda (n e)
+	     (context-url (markup-option n :url) (markup-option n :text) e)))
+
+;;//;*---------------------------------------------------------------------*/
+;;//;*    line-ref ...                                                     */
+;;//;*---------------------------------------------------------------------*/
+;;//(markup-writer 'line-ref
+;;//   :options '(:offset)
+;;//   :before "{\\textit{"
+;;//   :action (lambda (n e)
+;;//	      (let ((o (markup-option n :offset))
+;;//		    (v (string->number (markup-option n :text))))
+;;//		 (cond
+;;//		    ((and (number? o) (number? v))
+;;//		     (display (+ o v)))
+;;//		    (else
+;;//		     (display v)))))
+;;//   :after "}}")
+
+
+;;;; ======================================================================
+;;;;	&the-bibliography ...
+;;;; ======================================================================
+(markup-writer '&the-bibliography
+   :before "\n% Bibliography\n\n")
+
+
+;;;; ======================================================================
+;;;;	&bib-entry ...
+;;;; ======================================================================
+(markup-writer '&bib-entry
+   :options '(:title)
+   :action (lambda (n e)
+	     (skribe-eval (mark (markup-ident n)) e)
+	     (output n e (markup-writer-get '&bib-entry-label e))
+	     (output n e (markup-writer-get '&bib-entry-body e)))
+   :after "\n\n")
+
+;;;; ======================================================================
+;;;;	&bib-entry-label ...
+;;;; ======================================================================
+(markup-writer '&bib-entry-label
+   :options '(:title)
+   :before (lambda (n e) (output "[" e))
+   :action (lambda (n e) (output (markup-option n :title) e))
+   :after  (lambda (n e) (output "] "e)))
+
+;;;; ======================================================================
+;;;;	&bib-entry-title ...
+;;;; ======================================================================
+(markup-writer '&bib-entry-title
+   :action (lambda (n e)
+	     (let* ((t  (bold (markup-body n)))
+		    (en (handle-ast (ast-parent n)))
+		    (url #f ) ;;;;;;;;;;;;;;;// (markup-option en 'url))
+		    (ht (if url (ref :url (markup-body url) :text t) t)))
+	       (skribe-eval ht e))))
+
+
+;;//;*---------------------------------------------------------------------*/
+;;//;*    &bib-entry-url ...                                               */
+;;//;*---------------------------------------------------------------------*/
+;;//(markup-writer '&bib-entry-url
+;;//   :action (lambda (n e)
+;;//	      (let* ((en (handle-ast (ast-parent n)))
+;;//		     (url (markup-option en 'url))
+;;//		     (t (bold (markup-body url))))
+;;//		 (skribe-eval (ref :url (markup-body url) :text t) e))))
+
+
+;;;; ======================================================================
+;;;;	&the-index ...
+;;;; ======================================================================
+(markup-writer '&the-index
+   :options '(:column)
+   :action
+   (lambda (n e)
+     (define (make-mark-entry n)
+       (display "\\blank[medium]\n{\\bf\\it\\tfc{")
+       (skribe-eval (bold n) e)
+       (display "}}\\crlf\n"))
+
+     (define (make-primary-entry n)
+       (let ((b (markup-body n)))
+	 (markup-option-add! b :text (list (markup-option b :text) ", "))
+	 (markup-option-add! b :page #t)
+	 (output n e)))
+
+     (define (make-secondary-entry n)
+       (let* ((note (markup-option n :note))
+	      (b    (markup-body n))
+	      (bb   (markup-body b)))
+	 (if note
+	     (begin   ;; This is another entry
+	       (display "\\crlf\n ... ")
+	       (markup-option-add! b :text (list note ", ")))
+	     (begin   ;; another line on an entry
+	       (markup-option-add! b :text ", ")))
+	 (markup-option-add! b :page #t)
+	 (output n e)))
+
+     ;; Writer body starts here
+     (let ((col  (markup-option n :column)))
+       (when col
+	 (printf "\\startcolumns[n=~a]\n" col))
+       (for-each (lambda (item)
+		   ;;(DEBUG "ITEM= ~S" item)
+		   (if (pair? item)
+		       (begin
+			 (make-primary-entry (car item))
+			 (for-each (lambda (x) (make-secondary-entry x))
+				   (cdr item)))
+		       (make-mark-entry item))
+		   (display "\\crlf\n"))
+		 (markup-body n))
+       (when col
+	 (printf "\\stopcolumns\n")))))
+
+;;;; ======================================================================
+;;;;    &source-comment ...
+;;;; ======================================================================
+(markup-writer '&source-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (it (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-line-comment ...
+;;;; ======================================================================
+(markup-writer '&source-line-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-keyword ...
+;;;; ======================================================================
+(markup-writer '&source-keyword
+   :action (lambda (n e)
+	      (skribe-eval (it (markup-body n)) e)))
+
+;;;; ======================================================================
+;;;;    &source-error ...
+;;;; ======================================================================
+(markup-writer '&source-error
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-error-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'error-color) cc)
+			     (color :fg cc (it n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-define ...
+;;;; ======================================================================
+(markup-writer '&source-define
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-define-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-module ...
+;;;; ======================================================================
+(markup-writer '&source-module
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-module-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-markup ...
+;;;; ======================================================================
+(markup-writer '&source-markup
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-markup-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-thread ...
+;;;; ======================================================================
+(markup-writer '&source-thread
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-thread-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-string ...
+;;;; ======================================================================
+(markup-writer '&source-string
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-string-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-bracket ...
+;;;; ======================================================================
+(markup-writer '&source-bracket
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-bracket-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-type ...
+;;;; ======================================================================
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-key ...
+;;;; ======================================================================
+(markup-writer '&source-key
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;;;; ======================================================================
+;;;;    &source-type ...
+;;;; ======================================================================
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg "red" (bold n1))
+			     (bold n1))))
+		 (skribe-eval n2 e))))
+
+
+
+;;;; ======================================================================
+;;;;	Context Only Markups
+;;;; ======================================================================
+
+;;;
+;;; Margin -- put text in the margin
+;;;
+(define-markup (margin #!rest opts #!key (ident #f) (class "margin")
+			(side 'right) text)
+  (new markup
+       (markup 'margin)
+       (ident (or ident (symbol->string (gensym 'ident))))
+       (class class)
+       (required-options '(:text))
+       (options (the-options opts :ident :class))
+       (body (the-body opts))))
+
+(markup-writer 'margin
+   :options '(:text)
+   :before (lambda (n e)
+	     (display
+	      "\\setupinmargin[align=right,style=\\tfx\\setupinterlinespace]\n")
+	     (display "\\inright{")
+	     (output (markup-option n :text) e)
+	     (display "}{"))
+   :after  "}")
+
+;;;
+;;; ConTeXt and TeX
+;;;
+(define-markup (ConTeXt #!key (space #t))
+  (if (engine-format? "context")
+      (! (if space "\\CONTEXT\\ " "\\CONTEXT"))
+      "ConTeXt"))
+
+(define-markup (TeX #!key (space #t))
+  (if (engine-format? "context")
+      (! (if space "\\TEX\\ " "\\TEX"))
+      "ConTeXt"))
+
+;;;; ======================================================================
+;;;;    Restore the base engine
+;;;; ======================================================================
+(default-engine-set! (find-engine 'base))
diff --git a/src/guile/skribilo/engine/html.scm b/src/guile/skribilo/engine/html.scm
new file mode 100644
index 0000000..6232b96
--- /dev/null
+++ b/src/guile/skribilo/engine/html.scm
@@ -0,0 +1,2313 @@
+;;; html.scm  --  HTML engine.
+;;;
+;;; Copyright 2003, 2004  Manuel Serrano
+;;; Copyright 2005, 2006  Ludovic Courtès <ludovic.courtes@laas.fr>
+;;;
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;; USA.
+
+(define-skribe-module (skribilo engine html)
+  :autoload   (skribilo parameters) (*destination-file*)
+  :use-module ((srfi srfi-19) :renamer (symbol-prefix-proc 's19:)))
+
+
+;; Keep a reference to the base engine.
+(define base-engine (find-engine 'base))
+
+(if (not (engine? base-engine))
+    (error "bootstrap problem: base engine broken" base-engine))
+
+;*---------------------------------------------------------------------*/
+;*    html-file-default ...                                            */
+;*---------------------------------------------------------------------*/
+(define html-file-default
+   ;; Default implementation of the `file-name-proc' custom.
+   (let ((table '())
+	 (filename (tmpnam)))
+      (define (get-file-name base suf)
+	(let* ((c (assoc base table))
+	       (n (if (pair? c)
+		      (let ((n (+ 1 (cdr c))))
+			 (set-cdr! c n)
+			 n)
+		      (begin
+			 (set! table (cons (cons base 1) table))
+			 1))))
+	   (format #f "~a-~a.~a" base n suf)))
+      (lambda (node e)
+	(let ((f (markup-option node filename))
+	      (file (markup-option node :file)))
+	   (cond
+	      ((string? f)
+	       f)
+	      ((string? file)
+	       file)
+	      ((or file
+		   (and (is-markup? node 'chapter)
+			(engine-custom e 'chapter-file))
+		   (and (is-markup? node 'section)
+			(engine-custom e 'section-file))
+		   (and (is-markup? node 'subsection)
+			(engine-custom e 'subsection-file))
+		   (and (is-markup? node 'subsubsection)
+			(engine-custom e 'subsubsection-file)))
+	       (let* ((b (or (and (string? (*destination-file*))
+				  (prefix (*destination-file*)))
+			     ""))
+		      (s (or (and (string? (*destination-file*))
+				  (suffix (*destination-file*)))
+			     "html"))
+		      (nm (get-file-name b s)))
+		  (markup-option-add! node filename nm)
+		  nm))
+	      ((document? node)
+	       (*destination-file*))
+	      (else
+	       (let ((p (ast-parent node)))
+		  (if (container? p)
+		      (let ((file (html-file p e)))
+			 (if (string? file)
+			     (begin
+				(markup-option-add! node filename file)
+				file)
+			     #f))
+		      #f))))))))
+
+;*---------------------------------------------------------------------*/
+;*    html-engine ...                                                  */
+;*---------------------------------------------------------------------*/
+(define-public html-engine
+   ;; setup the html engine
+   (default-engine-set!
+      (make-engine 'html
+	 :version 1.0
+	 :format "html"
+	 :delegate (find-engine 'base)
+	 :filter (make-string-replace '((#\< "&lt;")
+					(#\> "&gt;")
+					(#\& "&amp;")
+					(#\" "&quot;")
+					(#\@ "&#x40;")))
+	 :custom `(;; the icon associated with the URL
+		   (favicon #f)
+		   ;; charset used
+		   (charset "ISO-8859-1")
+		   ;; enable/disable Javascript
+		   (javascript #f)
+		   ;; user html head
+		   (head #f)
+		   ;; user CSS
+		   (css ())
+		   ;; user inlined CSS
+		   (inline-css ())
+		   ;; user JS
+		   (js ())
+		   ;; emit-sui
+		   (emit-sui #f)
+		   ;; the body
+		   (background #f)
+		   (foreground #f)
+		   ;; the margins
+		   (margin-padding 3)
+		   (left-margin #f)
+		   (chapter-left-margin #f)
+		   (section-left-margin #f)
+		   (left-margin-font #f)
+		   (left-margin-size 17.)
+		   (left-margin-background #f)
+		   (left-margin-foreground #f)
+		   (right-margin #f)
+		   (chapter-right-margin #f)
+		   (section-right-margin #f)
+		   (right-margin-font #f)
+		   (right-margin-size 17.)
+		   (right-margin-background #f)
+		   (right-margin-foreground #f)
+		   ;; author configuration
+		   (author-font #f)
+		   ;; title configuration
+		   (title-font #f)
+		   (title-background #f)
+		   (title-foreground #f)
+		   (file-title-separator " -- ")
+		   ;; html file naming
+		   (file-name-proc ,html-file-default)
+		   ;; index configuration
+		   (index-header-font-size #f) ;; +2.
+		   ;; chapter configuration
+		   (chapter-number->string number->string)
+		   (chapter-file #f)
+		   ;; section configuration
+		   (section-title-start "<h3>")
+		   (section-title-stop "</h3>")
+		   (section-title-background #f)
+		   (section-title-foreground #f)
+		   (section-title-number-separator " ")
+		   (section-number->string number->string)
+		   (section-file #f)
+		   ;; subsection configuration
+		   (subsection-title-start "<h3>")
+		   (subsection-title-stop "</h3>")
+		   (subsection-title-background #f)
+		   (subsection-title-foreground #f)
+		   (subsection-title-number-separator " ")
+		   (subsection-number->string number->string)
+		   (subsection-file #f)
+		   ;; subsubsection configuration
+		   (subsubsection-title-start "<h4>")
+		   (subsubsection-title-stop "</h4>")
+		   (subsubsection-title-background #f)
+		   (subsubsection-title-foreground #f)
+		   (subsubsection-title-number-separator " ")
+		   (subsubsection-number->string number->string)
+		   (subsubsection-file #f)
+		   ;; source fontification
+		   (source-color #t)
+		   (source-comment-color "#ffa600")
+		   (source-error-color "red")
+		   (source-define-color "#6959cf")
+		   (source-module-color "#1919af")
+		   (source-markup-color "#1919af")
+		   (source-thread-color "#ad4386")
+		   (source-string-color "red")
+		   (source-bracket-color "red")
+		   (source-type-color "#00cf00")
+		   ;; image
+		   (image-format ("png" "gif" "jpg" "jpeg")))
+	 :symbol-table '(("iexcl" "&#161;")
+			 ("cent" "&#162;")
+			 ("pound" "&#163;")
+			 ("currency" "&#164;")
+			 ("yen" "&#165;")
+			 ("section" "&#167;")
+			 ("mul" "&#168;")
+			 ("copyright" "&#169;")
+			 ("female" "&#170;")
+			 ("lguillemet" "&#171;")
+			 ("not" "&#172;")
+			 ("registered" "&#174;")
+			 ("degree" "&#176;")
+			 ("plusminus" "&#177;")
+			 ("micro" "&#181;")
+			 ("paragraph" "&#182;")
+			 ("middot" "&#183;")
+			 ("male" "&#184;")
+			 ("rguillemet" "&#187;")
+			 ("1/4" "&#188;")
+			 ("1/2" "&#189;")
+			 ("3/4" "&#190;")
+			 ("iquestion" "&#191;")
+			 ("Agrave" "&#192;")
+			 ("Aacute" "&#193;")
+			 ("Acircumflex" "&#194;")
+			 ("Atilde" "&#195;")
+			 ("Amul" "&#196;")
+			 ("Aring" "&#197;")
+			 ("AEligature" "&#198;")
+			 ("Oeligature" "&#338;")
+			 ("Ccedilla" "&#199;")
+			 ("Egrave" "&#200;")
+			 ("Eacute" "&#201;")
+			 ("Ecircumflex" "&#202;")
+			 ("Euml" "&#203;")
+			 ("Igrave" "&#204;")
+			 ("Iacute" "&#205;")
+			 ("Icircumflex" "&#206;")
+			 ("Iuml" "&#207;")
+			 ("ETH" "&#208;")
+			 ("Ntilde" "&#209;")
+			 ("Ograve" "&#210;")
+			 ("Oacute" "&#211;")
+			 ("Ocurcumflex" "&#212;")
+			 ("Otilde" "&#213;")
+			 ("Ouml" "&#214;")
+			 ("times" "&#215;")
+			 ("Oslash" "&#216;")
+			 ("Ugrave" "&#217;")
+			 ("Uacute" "&#218;")
+			 ("Ucircumflex" "&#219;")
+			 ("Uuml" "&#220;")
+			 ("Yacute" "&#221;")
+			 ("THORN" "&#222;")
+			 ("szlig" "&#223;")
+			 ("agrave" "&#224;")
+			 ("aacute" "&#225;")
+			 ("acircumflex" "&#226;")
+			 ("atilde" "&#227;")
+			 ("amul" "&#228;")
+			 ("aring" "&#229;")
+			 ("aeligature" "&#230;")
+			 ("oeligature" "&#339;")
+			 ("ccedilla" "&#231;")
+			 ("egrave" "&#232;")
+			 ("eacute" "&#233;")
+			 ("ecircumflex" "&#234;")
+			 ("euml" "&#235;")
+			 ("igrave" "&#236;")
+			 ("iacute" "&#237;")
+			 ("icircumflex" "&#238;")
+			 ("iuml" "&#239;")
+			 ("eth" "&#240;")
+			 ("ntilde" "&#241;")
+			 ("ograve" "&#242;")
+			 ("oacute" "&#243;")
+			 ("ocurcumflex" "&#244;")
+			 ("otilde" "&#245;")
+			 ("ouml" "&#246;")
+			 ("divide" "&#247;")
+			 ("oslash" "&#248;")
+			 ("ugrave" "&#249;")
+			 ("uacute" "&#250;")
+			 ("ucircumflex" "&#251;")
+			 ("uuml" "&#252;")
+			 ("yacute" "&#253;")
+			 ("thorn" "&#254;")
+			 ("ymul" "&#255;")
+			 ;; Greek
+			 ("Alpha" "&#913;")
+			 ("Beta" "&#914;")
+			 ("Gamma" "&#915;")
+			 ("Delta" "&#916;")
+			 ("Epsilon" "&#917;")
+			 ("Zeta" "&#918;")
+			 ("Eta" "&#919;")
+			 ("Theta" "&#920;")
+			 ("Iota" "&#921;")
+			 ("Kappa" "&#922;")
+			 ("Lambda" "&#923;")
+			 ("Mu" "&#924;")
+			 ("Nu" "&#925;")
+			 ("Xi" "&#926;")
+			 ("Omicron" "&#927;")
+			 ("Pi" "&#928;")
+			 ("Rho" "&#929;")
+			 ("Sigma" "&#931;")
+			 ("Tau" "&#932;")
+			 ("Upsilon" "&#933;")
+			 ("Phi" "&#934;")
+			 ("Chi" "&#935;")
+			 ("Psi" "&#936;")
+			 ("Omega" "&#937;")
+			 ("alpha" "&#945;")
+			 ("beta" "&#946;")
+			 ("gamma" "&#947;")
+			 ("delta" "&#948;")
+			 ("epsilon" "&#949;")
+			 ("zeta" "&#950;")
+			 ("eta" "&#951;")
+			 ("theta" "&#952;")
+			 ("iota" "&#953;")
+			 ("kappa" "&#954;")
+			 ("lambda" "&#955;")
+			 ("mu" "&#956;")
+			 ("nu" "&#957;")
+			 ("xi" "&#958;")
+			 ("omicron" "&#959;")
+			 ("pi" "&#960;")
+			 ("rho" "&#961;")
+			 ("sigmaf" "&#962;")
+			 ("sigma" "&#963;")
+			 ("tau" "&#964;")
+			 ("upsilon" "&#965;")
+			 ("phi" "&#966;")
+			 ("chi" "&#967;")
+			 ("psi" "&#968;")
+			 ("omega" "&#969;")
+			 ("thetasym" "&#977;")
+			 ("piv" "&#982;")
+			 ;; punctuation
+			 ("bullet" "&#8226;")
+			 ("ellipsis" "&#8230;")
+			 ("weierp" "&#8472;")
+			 ("image" "&#8465;")
+			 ("real" "&#8476;")
+			 ("tm" "&#8482;")
+			 ("alef" "&#8501;")
+			 ("<-" "&#8592;")
+			 ("<--" "&#8592;")
+			 ("uparrow" "&#8593;")
+			 ("->" "&#8594;")
+			 ("-->" "&#8594;")
+			 ("downarrow" "&#8595;")
+			 ("<->" "&#8596;")
+			 ("<-->" "&#8596;")
+			 ("<+" "&#8629;")
+			 ("<=" "&#8656;")
+			 ("<==" "&#8656;")
+			 ("Uparrow" "&#8657;")
+			 ("=>" "&#8658;")
+			 ("==>" "&#8658;")
+			 ("Downarrow" "&#8659;")
+			 ("<=>" "&#8660;")
+			 ("<==>" "&#8660;")
+			 ;; Mathematical operators
+			 ("forall" "&#8704;")
+			 ("partial" "&#8706;")
+			 ("exists" "&#8707;")
+			 ("emptyset" "&#8709;")
+			 ("infinity" "&#8734;")
+			 ("nabla" "&#8711;")
+			 ("in" "&#8712;")
+			 ("notin" "&#8713;")
+			 ("ni" "&#8715;")
+			 ("prod" "&#8719;")
+			 ("sum" "&#8721;")
+			 ("asterisk" "&#8727;")
+			 ("sqrt" "&#8730;")
+			 ("propto" "&#8733;")
+			 ("angle" "&#8736;")
+			 ("and" "&#8743;")
+			 ("or" "&#8744;")
+			 ("cap" "&#8745;")
+			 ("cup" "&#8746;")
+			 ("integral" "&#8747;")
+			 ("therefore" "&#8756;")
+			 ("models" "|=")
+			 ("vdash" "|-")
+			 ("dashv" "-|")
+			 ("sim" "&#8764;")
+			 ("cong" "&#8773;")
+			 ("approx" "&#8776;")
+			 ("neq" "&#8800;")
+			 ("equiv" "&#8801;")
+			 ("le" "&#8804;")
+			 ("ge" "&#8805;")
+			 ("subset" "&#8834;")
+			 ("supset" "&#8835;")
+			 ("nsupset" "&#8835;")
+			 ("subseteq" "&#8838;")
+			 ("supseteq" "&#8839;")
+			 ("oplus" "&#8853;")
+			 ("otimes" "&#8855;")
+			 ("perp" "&#8869;")
+			 ("mid" "|")
+			 ("lceil" "&#8968;")
+			 ("rceil" "&#8969;")
+			 ("lfloor" "&#8970;")
+			 ("rfloor" "&#8971;")
+			 ("langle" "&#9001;")
+			 ("rangle" "&#9002;")
+			 ;; Misc
+			 ("loz" "&#9674;")
+			 ("spades" "&#9824;")
+			 ("clubs" "&#9827;")
+			 ("hearts" "&#9829;")
+			 ("diams" "&#9830;")
+			 ("euro" "&#8464;")
+			 ;; LaTeX
+			 ("dag" "dag")
+			 ("ddag" "ddag")
+			 ("circ" "o")
+			 ("top" "T")
+			 ("bottom" "&#8869;")
+			 ("lhd" "<")
+			 ("rhd" ">")
+			 ("parallel" "||")))))
+
+;*---------------------------------------------------------------------*/
+;*    html-file ...                                                    */
+;*---------------------------------------------------------------------*/
+(define (html-file n e)
+  (let ((proc (or (engine-custom e 'file-name-proc) html-file-default)))
+    (proc n e)))
+
+;*---------------------------------------------------------------------*/
+;*    html-title-engine ...                                            */
+;*---------------------------------------------------------------------*/
+(define html-title-engine
+   (copy-engine 'html-title base-engine
+      :filter (make-string-replace '((#\< "&lt;")
+				     (#\> "&gt;")
+				     (#\& "&amp;")
+				     (#\" "&quot;")))))
+
+;*---------------------------------------------------------------------*/
+;*    html-browser-title ...                                           */
+;*---------------------------------------------------------------------*/
+(define (html-browser-title n)
+   (and (markup? n)
+	(or (markup-option n :html-title)
+	    (if (document? n)
+		(markup-option n :title)
+		(html-browser-title (ast-parent n))))))
+
+
+;*---------------------------------------------------------------------*/
+;*    html-container-number ...                                        */
+;*    -------------------------------------------------------------    */
+;*    Returns a string representing the container number               */
+;*---------------------------------------------------------------------*/
+(define (html-container-number c e)
+   (define (html-number n proc)
+      (cond
+	 ((string? n)
+	  n)
+	 ((number? n)
+	  (if (procedure? proc)
+	      (proc n)
+	      (number->string n)))
+	 (else
+	  "")))
+   (define (html-chapter-number c)
+      (html-number (markup-option c :number)
+		   (engine-custom e 'chapter-number->string)))
+   (define (html-section-number c)
+      (let ((p (ast-parent c))
+	    (s (html-number (markup-option c :number)
+			    (engine-custom e 'section-number->string))))
+	 (cond
+	    ((is-markup? p 'chapter)
+	     (string-append (html-chapter-number p) "." s))
+	    (else
+	     (string-append s)))))
+   (define (html-subsection-number c)
+      (let ((p (ast-parent c))
+	    (s (html-number (markup-option c :number)
+			    (engine-custom e 'subsection-number->string))))
+	 (cond
+	    ((is-markup? p 'section)
+	     (string-append (html-section-number p) "." s))
+	    (else
+	     (string-append "." s)))))
+   (define (html-subsubsection-number c)
+      (let ((p (ast-parent c))
+	    (s (html-number (markup-option c :number)
+			    (engine-custom e 'subsubsection-number->string))))
+	 (cond
+	    ((is-markup? p 'subsection)
+	     (string-append (html-subsection-number p) "." s))
+	    (else
+	     (string-append ".." s)))))
+   (define (inner-html-container-number c)
+      (html-number (markup-option c :number) #f))
+   (let ((n (markup-option c :number)))
+      (if (not n)
+	  ""
+	  (case (markup-markup c)
+	     ((chapter)
+	      (html-chapter-number c))
+	     ((section)
+	      (html-section-number c))
+	     ((subsection)
+	      (html-subsection-number c))
+	     ((subsubsection)
+	      (html-subsubsection-number c))
+	     (else
+	      (if (container? c)
+		  (inner-html-container-number c)
+		  (skribe-error 'html-container-number
+				"Not a container"
+				(markup-markup c))))))))
+
+;*---------------------------------------------------------------------*/
+;*    html-counter ...                                                 */
+;*---------------------------------------------------------------------*/
+(define (html-counter cnts)
+   (cond
+      ((not cnts)
+       "")
+      ((null? cnts)
+       "")
+      ((not (pair? cnts))
+       cnts)
+      ((null? (cdr cnts))
+       (format #f "~a." (car cnts)))
+      (else
+       (let loop ((cnts cnts))
+	  (if (null? (cdr cnts))
+	      (format #f "~a" (car cnts))
+	      (format #f "~a.~a" (car cnts) (loop (cdr cnts))))))))
+
+;*---------------------------------------------------------------------*/
+;*    html-width ...                                                   */
+;*---------------------------------------------------------------------*/
+(define-public (html-width width)
+   (cond
+      ((and (integer? width) (exact? width))
+       (format #f "~A" width))
+      ((real? width)
+       (format #f "~A%" (inexact->exact (round width))))
+      ((string? width)
+       width)
+      (else
+       (skribe-error 'html-width "bad width" width))))
+
+;*---------------------------------------------------------------------*/
+;*    html-class ...                                                   */
+;*---------------------------------------------------------------------*/
+(define-public (html-class m)
+   (if (markup? m)
+       (let ((c (markup-class m)))
+	  (if (or (string? c) (symbol? c) (number? c))
+	      (printf " class=\"~a\"" c)))))
+
+;*---------------------------------------------------------------------*/
+;*    html-markup-class ...                                            */
+;*---------------------------------------------------------------------*/
+(define-public (html-markup-class m)
+   (lambda (n e)
+      (printf "<~a" m)
+      (html-class n)
+      (display ">")))
+
+;*---------------------------------------------------------------------*/
+;*    html-color-spec? ...                                             */
+;*---------------------------------------------------------------------*/
+(define (html-color-spec? v)
+   (and v
+	(not (unspecified? v))
+	(or (not (string? v)) (> (string-length v) 0))))
+
+;*---------------------------------------------------------------------*/
+;*    document ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'document
+   :options '(:title :author :ending :html-title :env :keywords)
+   :action (lambda (n e)
+	      (let* ((id (markup-ident n))
+		     (title (new markup
+			       (markup '&html-document-title)
+			       (parent n)
+			       (ident (string-append id "-title"))
+			       (class (markup-class n))
+			       (options `((author ,(markup-option n :author))))
+			       (body (markup-option n :title)))))
+		 (&html-generic-document n title e)))
+   :after (lambda (n e)
+	     (if (engine-custom e 'emit-sui)
+		 (document-sui n e))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-html ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-html
+   :before "<!-- 95% W3C COMPLIANT, 95% CSS FREE, RAW HTML -->
+<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+<html>\n"
+   :after "</html>")
+
+;*---------------------------------------------------------------------*/
+;*    &html-head ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-head
+   :before (lambda (n e)
+             (printf "<head>\n")
+             (printf "<meta http-equiv=\"Content-Type\" content=\"text/html;")
+             (printf "charset=~A\">\n" (engine-custom (find-engine 'html)
+                                                      'charset)))
+   :after "</head>\n\n")
+
+;*---------------------------------------------------------------------*/
+;*    &html-meta ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-meta
+   :before "<meta name=\"keywords\" content=\""
+   :action (lambda (n e)
+             (let ((kw* (map ast->string (or (markup-body n) '()))))
+               (output (keyword-list->comma-separated kw*) e)))
+   :after  "\">\n")
+
+;*---------------------------------------------------------------------*/
+;*    &html-body ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-body
+   :before (lambda (n e)
+	      (let ((bg (engine-custom e 'background)))
+		 (display "<body")
+		 (html-class n)
+		 (when (html-color-spec? bg) (printf " bgcolor=\"~a\"" bg))
+		 (display ">\n")))
+   :after "</body>\n")
+
+;*---------------------------------------------------------------------*/
+;*    &html-page ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-page
+   :action (lambda (n e)
+	      (define (html-margin m fn size bg fg cla)
+		 (printf "<td align=\"left\" valign=\"top\" class=\"~a\"" cla)
+		 (if size
+		     (printf " width=\"~a\"" (html-width size)))
+		 (if (html-color-spec? bg)
+		     (printf " bgcolor=\"~a\">" bg)
+		     (display ">"))
+		 (printf "<div class=\"~a\">\n" cla)
+		 (cond
+		    ((and (string? fg) (string? fn))
+		     (printf "<font color=\"~a\" \"~a\">" fg fn))
+		    ((string? fg)
+		     (printf "<font color=\"~a\">" fg))
+		    ((string? fn)
+		     (printf "<font \"~a\">" fn)))
+		 (if (procedure? m)
+		     (skribe-eval (m n e) e)
+		     (output m e))
+		 (if (or (string? fg) (string? fn))
+		     (display "</font>"))
+		 (display "</div></td>\n"))
+	      (let ((body (markup-body n))
+		    (lm (engine-custom e 'left-margin))
+		    (lmfn (engine-custom e 'left-margin-font))
+		    (lms (engine-custom e 'left-margin-size))
+		    (lmbg (engine-custom e 'left-margin-background))
+		    (lmfg (engine-custom e 'left-margin-foreground))
+		    (rm (engine-custom e 'right-margin))
+		    (rmfn (engine-custom e 'right-margin-font))
+		    (rms (engine-custom e 'right-margin-size))
+		    (rmbg (engine-custom e 'right-margin-background))
+		    (rmfg (engine-custom e 'right-margin-foreground)))
+		 (cond
+		    ((and lm rm)
+		     (let* ((ep (engine-custom e 'margin-padding))
+			    (ac (if (number? ep) ep 0)))
+			(printf "<table cellpadding=\"~a\" cellspacing=\"0\" width=\"100%\" class=\"skribilo-margins\"><tr>\n" ac))
+		     (html-margin lm lmfn lms lmbg lmfg "skribilo-left-margin")
+		     (html-margin body #f #f #f #f "skribilo-body")
+		     (html-margin rm rmfn rms rmbg rmfg "skribilo-right-margin")
+		     (display "</tr></table>"))
+		    (lm
+		     (let* ((ep (engine-custom e 'margin-padding))
+			    (ac (if (number? ep) ep 0)))
+			(printf "<table cellpadding=\"~a\" cellspacing=\"0\" width=\"100%\" class=\"skribilo-margins\"><tr>\n" ac))
+		     (html-margin lm lmfn lms lmbg lmfg "skribilo-left-margin")
+		     (html-margin body #f #f #f #f "skribilo-body")
+		     (display "</tr></table>"))
+		    (rm
+		     (let* ((ep (engine-custom e 'margin-padding))
+			    (ac (if (number? ep) ep 0)))
+			(printf "<table cellpadding=\"~a\" cellspacing=\"0\" width=\"100%\" class=\"skribilo-margins\"><tr>\n"))
+		     (html-margin body #f #f #f #f "skribilo-body")
+		     (html-margin rm rmfn rms rmbg rmfg "skribilo-right-margin")
+		     (display "</tr></table>"))
+		    (else
+		     (display "<div class=\"skribilo-body\">\n")
+		     (output body e)
+		     (display "</div>\n"))))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-generic-header ...                                         */
+;*---------------------------------------------------------------------*/
+(define (&html-generic-header n e)
+   (let* ((ic (engine-custom e 'favicon))
+	  (id (markup-ident n)))
+      (unless (string? id)
+	 (skribe-error '&html-generic-header
+		       (format #f "Illegal identifier `~a'" id)
+		       n))
+      ;; title
+      (output (new markup
+		 (markup '&html-header-title)
+		 (parent n)
+		 (ident (string-append id "-title"))
+		 (class (markup-class n))
+		 (body (markup-body n)))
+	      e)
+      ;; favicon
+      (output (new markup
+		 (markup '&html-header-favicon)
+		 (parent n)
+		 (ident (string-append id "-favicon"))
+		 (body (cond
+			  ((string? ic)
+			   ic)
+			  ((procedure? ic)
+			   (ic d e))
+			  (else #f))))
+	      e)
+      ;; style
+      (output (new markup
+		 (markup '&html-header-style)
+		 (parent n)
+		 (ident (string-append id "-style"))
+		 (class (markup-class n)))
+	      e)
+      ;; css
+      (output (new markup
+		 (markup '&html-header-css)
+		 (parent n)
+		 (ident (string-append id "-css"))
+		 (body (let ((c (engine-custom e 'css)))
+			  (if (string? c)
+			      (list c)
+			      c))))
+	      e)
+      ;; javascript
+      (output (new markup
+		 (markup '&html-header-javascript)
+		 (parent n)
+		 (ident (string-append id "-javascript")))
+	      e)))
+
+(markup-writer '&html-header-title
+   :before "<title>"
+   :action (lambda (n e)
+	      (output (markup-body n) html-title-engine))
+   :after "</title>\n")
+
+(markup-writer '&html-header-favicon
+   :action (lambda (n e)
+	      (let ((i (markup-body n)))
+		 (when i
+		    (printf " <link rel=\"shortcut icon\" href=~s>\n" i)))))
+
+(markup-writer '&html-header-css
+   :action (lambda (n e)
+	      (let ((css (markup-body n)))
+		 (when (pair? css)
+		    (for-each (lambda (css)
+				 (printf " <link href=~s rel=\"stylesheet\" type=\"text/css\">\n" css))
+			      css)))))
+
+(markup-writer '&html-header-style
+   :before " <style type=\"text/css\">\n  <!--\n"
+   :action (lambda (n e)
+	      (let ((hd (engine-custom e 'head))
+		    (icss (let ((ic (engine-custom e 'inline-css)))
+			     (if (string? ic)
+				 (list ic)
+				 ic))))
+		 (display "  pre { font-family: monospace }\n")
+		 (display "  tt { font-family: monospace }\n")
+		 (display "  code { font-family: monospace }\n")
+		 (display "  p.flushright { text-align: right }\n")
+		 (display "  p.flushleft { text-align: left }\n")
+		 (display "  span.sc { font-variant: small-caps }\n")
+		 (display "  span.sf { font-family: sans-serif }\n")
+		 (display "  span.skribetitle { font-family: sans-serif; font-weight: bolder; font-size: x-large; }\n")
+		 (when hd (display (format #f "  ~a\n" hd)))
+		 (when (pair? icss)
+		    (for-each (lambda (css)
+				 (let ((p (open-input-file css)))
+				    (if (not (input-port? p))
+					(skribe-error
+					 'html-css
+					 "Can't open CSS file for input"
+					 css)
+					(begin
+					   (let loop ((l (read-line p)))
+					      (unless (eof-object? l)
+						 (display l)
+						 (newline)
+						 (loop (read-line p))))
+					   (close-input-port p)))))
+			      icss))))
+   :after "  -->\n </style>\n")
+
+(markup-writer '&html-header-javascript
+   :action (lambda (n e)
+	      (when (engine-custom e 'javascript)
+		 (display " <script language=\"JavaScript\" type=\"text/javascript\">\n")
+		 (display " <!--\n")
+		 (display "  function skribenospam( n, d, f ) {\n")
+		 (display "    nn=n.replace( / /g , \".\" );\n" )
+		 (display "    dd=d.replace( / /g , \".\" );\n" )
+		 (display "    document.write( \"<a href=\\\"mailto:\" + nn + \"@\" + dd + \"\\\">\" );\n")
+		 (display "    if( f ) {\n")
+		 (display "      document.write( \"<tt>\" + nn + \"@\" + dd + \"</\" + \"tt><\" + \"/a>\" );\n")
+		 (display "    }\n")
+		 (display "  }\n")
+		 (display " -->\n")
+		 (display " </script>\n"))
+	      (let* ((ejs (engine-custom e 'js))
+		     (js (cond
+			    ((string? ejs)
+			     (list ejs))
+			    ((list? ejs)
+			     ejs)
+			    (else
+			     '()))))
+		 (for-each (lambda (s)
+			      (printf "<script type=\"text/javascript\" src=\"~a\"></script>" s))
+			   js))))
+
+
+;*---------------------------------------------------------------------*/
+;*    &html-header ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-document-header :action &html-generic-header)
+(markup-writer '&html-chapter-header :action &html-generic-header)
+(markup-writer '&html-section-header :action &html-generic-header)
+(markup-writer '&html-subsection-header :action &html-generic-header)
+(markup-writer '&html-subsubsection-header :action &html-generic-header)
+
+;*---------------------------------------------------------------------*/
+;*    &html-ending ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-ending
+   :before "<div class=\"skribilo-ending\">"
+   :action (lambda (n e)
+	      (let ((body (markup-body n)))
+		 (if body
+		     (output body #t)
+		     (skribe-eval
+		      (list (hrule)
+			    (p :class "ending"
+			       (font :size -1
+				     (list "This HTML page was "
+					   "produced by "
+					   (ref :text "Skribilo"
+						:url (skribilo-url))
+					   "."
+					   (linebreak)
+					   "Last update: "
+					   (s19:date->string
+					    (s19:current-date))))))
+		      e))))
+   :after "</div>\n")
+
+;*---------------------------------------------------------------------*/
+;*    &html-generic-title ...                                          */
+;*---------------------------------------------------------------------*/
+(define (&html-generic-title n e)
+   (let* ((title (markup-body n))
+	  (authors (markup-option n 'author))
+	  (tbg (engine-custom e 'title-background))
+	  (tfg (engine-custom e 'title-foreground))
+	  (tfont (engine-custom e 'title-font)))
+      (when title
+	 (display "<table width=\"100%\" class=\"skribilo-title\" cellspacing=\"0\" cellpadding=\"0\"><tbody>\n<tr>")
+	 (if (html-color-spec? tbg)
+	     (printf "<td align=\"center\"~A>"
+                     (if (html-color-spec? tbg)
+                         (string-append "bgcolor=\"" tbg "\"")
+                         ""))
+	     (display "<td align=\"center\">"))
+	 (if (string? tfg)
+	     (printf "<font color=\"~a\">" tfg))
+	 (when title
+	    (if (string? tfont)
+		(begin
+		   (printf "<font ~a><strong>" tfont)
+		   (output title e)
+		   (display "</strong></font>"))
+		(begin
+		   (printf "<div class=\"skribilo-title\"><strong><big>")
+		   (output title e)
+		   (display "</big></strong></div>"))))
+	 (if (not authors)
+	     (display "\n")
+	     (html-title-authors authors e))
+	 (if (string? tfg)
+	     (display "</font>"))
+	 (display "</td></tr></tbody></table>\n"))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-document-title ...                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-document-title :action &html-generic-title)
+(markup-writer '&html-chapter-title :action &html-generic-title)
+(markup-writer '&html-section-title :action &html-generic-title)
+(markup-writer '&html-subsection-title :action &html-generic-title)
+(markup-writer '&html-subsubsection-title :action &html-generic-title)
+
+;*---------------------------------------------------------------------*/
+;*    &html-footnotes                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-footnotes
+   :before (lambda (n e)
+	      (let ((footnotes (markup-body n)))
+		 (when (pair? footnotes)
+		    (display "<div class=\"skribilo-footnote\">")
+		    (display "<br><br>\n")
+		    (display "<hr width='20%' size='2' align='left'>\n"))))
+   :action (lambda (n e)
+	      (let ((footnotes (markup-body n)))
+		 (when (pair? footnotes)
+		    (let loop ((fns footnotes))
+		       (if (pair? fns)
+			   (let ((fn (car fns)))
+			      (printf "<a name=\"footnote-~a\">"
+				      (string-canonicalize
+				       (container-ident fn)))
+			      (printf "<sup><small>~a</small></sup></a>: "
+				      (markup-option fn :number))
+			      (output (markup-body fn) e)
+			      (display "\n<br>\n")
+			      (loop (cdr fns)))))
+		    (display "<div>")))))
+
+;*---------------------------------------------------------------------*/
+;*    html-title-authors ...                                           */
+;*---------------------------------------------------------------------*/
+(define-public (html-title-authors authors e)
+   (define (html-authorsN authors cols first)
+      (define (make-row authors . opt)
+	 (tr (map (lambda (v)
+		     (apply td :align 'center :valign 'top v opt))
+		  authors)))
+      (define (make-rows authors)
+	 (let loop ((authors authors)
+		    (rows '())
+		    (row '())
+		    (cnum 0))
+	    (cond
+	       ((null? authors)
+		(reverse! (cons (make-row (reverse! row)) rows)))
+	       ((= cnum cols)
+		(loop authors
+		      (cons (make-row (reverse! row)) rows)
+		      '()
+		      0))
+	       (else
+		(loop (cdr authors)
+		      rows
+		      (cons (car authors) row)
+		      (+ cnum 1))))))
+      (output (table :cellpadding 10
+		 (if first
+		     (cons (make-row (list (car authors)) :colspan cols)
+			   (make-rows (cdr authors)))
+		     (make-rows authors)))
+	      e))
+   (cond
+      ((pair? authors)
+       (display "<center>\n")
+       (let ((len (length authors)))
+	  (case len
+	     ((1)
+	      (output (car authors) e))
+	     ((2 3)
+	      (html-authorsN authors len #f))
+	     ((4)
+	      (html-authorsN authors 2 #f))
+	     (else
+	      (html-authorsN authors 3 #t))))
+       (display "</center>\n"))
+      (else
+       (html-title-authors (list authors) e))))
+
+;*---------------------------------------------------------------------*/
+;*    document-sui ...                                                 */
+;*---------------------------------------------------------------------*/
+(define (document-sui n e)
+   (define (sui)
+      (display "(sui \"")
+      (skribe-eval (markup-option n :title) html-title-engine)
+      (display "\"\n")
+      (printf "  :file ~s\n" (sui-referenced-file n e))
+      (sui-marks n e)
+      (sui-blocks 'chapter n e)
+      (sui-blocks 'section n e)
+      (sui-blocks 'subsection n e)
+      (sui-blocks 'subsubsection n e)
+      (display "  )\n"))
+   (if (string? (*destination-file*))
+       (let ((f (format #f "~a.sui" (prefix (*destination-file*)))))
+	  (with-output-to-file f sui))
+       (sui)))
+
+;*---------------------------------------------------------------------*/
+;*    sui-referenced-file ...                                          */
+;*---------------------------------------------------------------------*/
+(define (sui-referenced-file n e)
+   (let ((file (html-file n e)))
+      (if (member (suffix file) '("skb" "sui" "skr" "html"))
+	  (string-append (strip-ref-base (prefix file)) ".html")
+	  file)))
+
+;*---------------------------------------------------------------------*/
+;*    sui-marks ...                                                    */
+;*---------------------------------------------------------------------*/
+(define (sui-marks n e)
+   (printf "  (marks")
+   (for-each (lambda (m)
+		(printf "\n    (~s" (markup-ident m))
+		(printf " :file ~s" (sui-referenced-file m e))
+		(printf " :mark ~s" (markup-ident m))
+		(when (markup-class m)
+		   (printf " :class ~s" (markup-class m)))
+		(display ")"))
+	     (search-down (lambda (n) (is-markup? n 'mark)) n))
+   (display ")\n"))
+
+;*---------------------------------------------------------------------*/
+;*    sui-blocks ...                                                   */
+;*---------------------------------------------------------------------*/
+(define (sui-blocks kind n e)
+   (printf "  (~as" kind)
+   (for-each (lambda (chap)
+		(display "\n    (\"")
+		(skribe-eval (markup-option chap :title) html-title-engine)
+		(printf "\" :file ~s" (sui-referenced-file chap e))
+		(printf " :mark ~s" (markup-ident chap))
+		(when (markup-class chap)
+		   (printf " :class ~s" (markup-class chap)))
+		(display ")"))
+	     (container-search-down (lambda (n) (is-markup? n kind)) n))
+   (display ")\n"))
+
+;*---------------------------------------------------------------------*/
+;*    author ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address :phone :photo :align)
+   :before (lambda (n e)
+	      (display "<table")
+	      (html-class n)
+	      (display "><tbody>\n"))
+   :action (lambda (n e)
+	      (let ((name (markup-option n :name))
+		    (title (markup-option n :title))
+		    (affiliation (markup-option n :affiliation))
+		    (email (markup-option n :email))
+		    (url (markup-option n :url))
+		    (address (markup-option n :address))
+		    (phone (markup-option n :phone))
+		    (nfn (engine-custom e 'author-font))
+		    (align (markup-option n :align)))
+		 (define (row n)
+		    (printf "<tr><td align=\"~a\">" align)
+		    (output n e)
+		    (display "</td></tr>"))
+		 ;; name
+		 (printf "<tr><td align=\"~a\">" align)
+		 (if nfn (printf "<font ~a>\n" nfn))
+		 (output name e)
+		 (if nfn (printf "</font>\n"))
+		 (display "</td></tr>")
+		 ;; title
+		 (if title (row title))
+		 ;; affiliation
+		 (if affiliation (row affiliation))
+		 ;; address
+		 (if (pair? address)
+		     (for-each row address))
+		 ;; telephone
+		 (if phone (row phone))
+		 ;; email
+		 (if email (row email))
+		 ;; url
+		 (if url (row url))))
+   :after "</tbody></table>")
+
+;*---------------------------------------------------------------------*/
+;*    author ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address :phone :photo :align)
+   :predicate (lambda (n e) (markup-option n :photo))
+   :before (lambda (n e)
+	      (display "<table")
+	      (html-class n)
+	      (display "><tbody>\n<tr>"))
+   :action (lambda (n e)
+	      (let ((photo (markup-option n :photo)))
+		 (display "<td>")
+		 (output photo e)
+		 (display "</td><td>")
+		 (markup-option-add! n :photo #f)
+		 (output n e)
+		 (markup-option-add! n :photo photo)
+		 (display "</td>")))
+   :after "</tr>\n</tbody></table>")
+
+;*---------------------------------------------------------------------*/
+;*    toc ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'toc
+   :options 'all
+   :action (lambda (n e)
+	      (define (col n)
+		 (let loop ((i 0))
+		      (if (< i n)
+			  (begin
+			     (display "<td></td>")
+			     (loop (+ i 1))))))
+	      (define (toc-entry fe level)
+		 (let* ((c (car fe))
+			(ch (cdr fe))
+			(t (markup-option c :title))
+			(id (markup-ident c))
+			(f (html-file c e)))
+		    (unless (string? id)
+		       (skribe-error 'toc
+				     (format #f "illegal identifier `~a'" id)
+				     c))
+		    (display " <tr>")
+		    ;; blank columns
+		    (col level)
+		    ;; number
+		    (printf "<td valign=\"top\" align=\"left\">~a</td>"
+			    (html-container-number c e))
+		    ;; title
+		    (printf "<td colspan=\"~a\" width=\"100%\">"
+			    (- 4 level))
+		    (printf "<a href=\"~a#~a\">"
+			    (if (and (*destination-file*)
+				     (string=? f (*destination-file*)))
+				""
+				(strip-ref-base (or f (*destination-file*) "")))
+			    (string-canonicalize id))
+		    (output (markup-option c :title) e)
+		    (display "</a></td>")
+		    (display "</tr>\n")
+		    ;; the children
+		    (for-each (lambda (n) (toc-entry n (+ 1 level))) ch)))
+
+	      (let* ((c (markup-option n :chapter))
+		     (s (markup-option n :section))
+		     (ss (markup-option n :subsection))
+		     (sss (markup-option n :subsubsection))
+		     (b (markup-body n))
+		     (bb (if (handle? b)
+			     (handle-ast b)
+			     b)))
+		 (if (not (container? bb))
+		     (error 'toc
+			    "Illegal body (container expected)"
+			    (if (markup? bb)
+				(markup-markup bb)
+				"???"))
+		     (let ((lst (find-down (lambda (x)
+					     (and (markup? x)
+						  (markup-option x :toc)
+						  (or (and sss (is-markup? x 'subsubsection))
+						      (and ss (is-markup? x 'subsection))
+						      (and s (is-markup? x 'section))
+						      (and c (is-markup? x 'chapter))
+						      (markup-option n (symbol->keyword
+									(markup-markup x))))))
+					   (container-body bb))))
+		       ;; avoid to produce an empty table
+		       (unless (null? lst)
+			  (display "<table cellspacing=\"1\" cellpadding=\"1\" width=\"100%\"")
+			  (html-class n)
+			  (display ">\n<tbody>\n")
+
+			  (for-each (lambda (n) (toc-entry n 0)) lst)
+
+			  (display "</tbody>\n</table>\n")))))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-generic-document ...                                       */
+;*---------------------------------------------------------------------*/
+(define (&html-generic-document n title e)
+   (let* ((id (markup-ident n))
+	  (header (new markup
+		     (markup '&html-chapter-header)
+		     (ident (string-append id "-header"))
+		     (class (markup-class n))
+		     (parent n)
+		     (body (html-browser-title n))))
+          (meta (new markup
+                   (markup '&html-meta)
+                   (ident (string-append id "-meta"))
+                   (class (markup-class n))
+                   (parent n)
+                   (body (markup-option (ast-document n) :keywords))))
+	  (head (new markup
+		   (markup '&html-head)
+		   (ident (string-append id "-head"))
+		   (class (markup-class n))
+		   (parent n)
+		   (body (list header meta))))
+	  (ftnote (new markup
+		     (markup '&html-footnotes)
+		     (ident (string-append id "-footnote"))
+		     (class (markup-class n))
+		     (parent n)
+		     (body (reverse!
+			    (container-env-get n 'footnote-env)))))
+	  (page (new markup
+		   (markup '&html-page)
+		   (ident (string-append id "-page"))
+		   (class (markup-class n))
+		   (parent n)
+		   (body (list (markup-body n) ftnote))))
+	  (ending (new markup
+		     (markup '&html-ending)
+		     (ident (string-append id "-ending"))
+		     (class (markup-class n))
+		     (parent n)
+		     (body (or (markup-option n :ending)
+			       (let ((p (ast-document n)))
+				  (and p (markup-option p :ending)))))))
+	  (body (new markup
+		   (markup '&html-body)
+		   (ident (string-append id "-body"))
+		   (class (markup-class n))
+		   (parent n)
+		   (body (list title page ending))))
+	  (html (new markup
+		   (markup '&html-html)
+		   (ident (string-append id "-html"))
+		   (class (markup-class n))
+		   (parent n)
+		   (body (list head body)))))
+      ;; No file must be opened for documents. These files are
+      ;; directly opened by Skribe
+      (if (document? n)
+	  (output html e)
+	  (with-output-to-file (html-file n e)
+	     (lambda ()
+		(output html e))))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-generic-subdocument ...                                    */
+;*---------------------------------------------------------------------*/
+(define (&html-generic-subdocument n e)
+   (let* ((p (ast-document n))
+	  (id (markup-ident n))
+	  (ti (let* ((nb (html-container-number n e))
+		     (tc (markup-option n :title))
+		     (ti (if (document? p)
+			     (list (markup-option p :title)
+				   (engine-custom e 'file-title-separator)
+				   tc)
+			     tc))
+		     (sep (engine-custom
+			     e
+			     (symbol-append (markup-markup n)
+					    '-title-number-separator)))
+		     (nti (and tc
+			       (if (and nb (not (equal? nb "")))
+				   (list nb
+					 (if (unspecified? sep) ". " sep)
+					 ti)
+				   ti))))
+		 (new markup
+		    (markup (symbol-append '&html- (markup-markup n) '-title))
+		    (ident (string-append id "-title"))
+		    (parent n)
+		    (options '((author ())))
+		    (body nti)))))
+      (case (markup-markup n)
+	 ((chapter)
+	  (skribe-message "  [~s chapter: ~a]\n" (engine-ident e) id))
+	 ((section)
+	  (skribe-message "    [~s section: ~a]\n" (engine-ident e) id)))
+      (&html-generic-document n ti e)))
+
+;*---------------------------------------------------------------------*/
+;*    chapter ... @label chapter@                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'chapter
+   :options '(:title :number :file :toc :html-title :env)
+   :before (lambda (n e)
+	      (let ((title (markup-option n :title))
+		    (ident (markup-ident n)))
+		 (display "<!-- ")
+		 (output title html-title-engine)
+		 (display " -->\n")
+		 (display "<a name=\"")
+		 (display (string-canonicalize ident))
+		 (display "\"></a>\n")
+		 (display "<center><h1")
+		 (html-class n)
+		 (display ">")
+		 (output (html-container-number n e) e)
+		 (display " ")
+		 (output (markup-option n :title) e)
+		 (display "</h1></center>")))
+   :after "<br>")
+
+;; This writer is invoked only for chapters rendered inside separate files!
+(markup-writer 'chapter
+   :options '(:title :number :file :toc :html-title :env)
+   :predicate (lambda (n e)
+		 (or (markup-option n :file)
+		     (engine-custom e 'chapter-file)))
+   :action &html-generic-subdocument)
+
+;*---------------------------------------------------------------------*/
+;*    html-section-title ...                                           */
+;*---------------------------------------------------------------------*/
+(define (html-section-title n e)
+   (let* ((title (markup-option n :title))
+	  (number (markup-option n :number))
+	  (c (markup-class n))
+	  (ident (markup-ident n))
+	  (kind (markup-markup n))
+	  (tbg (engine-custom e (symbol-append kind '-title-background)))
+	  (tfg (engine-custom e (symbol-append kind '-title-foreground)))
+	  (tstart (engine-custom e (symbol-append kind '-title-start)))
+	  (tstop (engine-custom e (symbol-append kind '-title-stop)))
+	  (nsep (engine-custom e (symbol-append kind '-title-number-separator))))
+      ;; the section header
+      (display "<!-- ")
+      (output title html-title-engine)
+      (display " -->\n")
+      (display "<a name=\"")
+      (display (string-canonicalize ident))
+      (display "\"></a>\n")
+      (if c
+	  (printf "<div class=\"~a-title\">" c)
+	  (printf "<div class=\"skribilo-~a-title\">" (markup-markup n)))
+      (when (html-color-spec? tbg)
+	 (display "<table width=\"100%\">")
+	 (printf "<tr><td bgcolor=\"~a\">" tbg))
+      (display tstart)
+      (if tfg (printf "<font color=\"~a\">" tfg))
+      (if number
+	  (begin
+	     (output (html-container-number n e) e)
+	     (output nsep e)))
+      (output title e)
+      (if tfg (display "</font>\n"))
+      (display tstop)
+      (when (and (string? tbg) (> (string-length tbg) 0))
+	 (display "</td></tr></table>\n"))
+      (display "</div>")
+      (display "<div")
+      (html-class n)
+      (display ">"))
+   (newline))
+
+;*---------------------------------------------------------------------*/
+;*    section ...  @label section@                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'section
+   :options '(:title :html-title :number :toc :file :env)
+   :before html-section-title
+   :after "</div><br>\n")
+
+;; on-file section writer
+(markup-writer 'section
+   :options '(:title :html-title :number :toc :file :env)
+   :predicate (lambda (n e)
+		 (or (markup-option n :file)
+		     (engine-custom e 'section-file)))
+   :action &html-generic-subdocument)
+
+;*---------------------------------------------------------------------*/
+;*    subsection ... @label subsection@                                */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsection
+   :options '(:title :html-title :number :toc :env :file)
+   :before html-section-title
+   :after "</div>\n")
+
+;; on-file subsection writer
+(markup-writer 'section
+   :options '(:title :html-title :number :toc :file :env)
+   :predicate (lambda (n e)
+		 (or (markup-option n :file)
+		     (engine-custom e 'subsection-file)))
+   :action &html-generic-subdocument)
+
+;*---------------------------------------------------------------------*/
+;*    subsubsection ... @label subsubsection@                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsubsection
+   :options '(:title :html-title :number :toc :env :file)
+   :before html-section-title
+   :after "</div>\n")
+
+;; on-file subsection writer
+(markup-writer 'section
+   :options '(:title :html-title :number :toc :file :env)
+   :predicate (lambda (n e)
+		 (or (markup-option n :file)
+		     (engine-custom e 'subsubsection-file)))
+   :action &html-generic-subdocument)
+
+;*---------------------------------------------------------------------*/
+;*    paragraph ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'paragraph
+   :before (lambda (n e)
+	      (when (and (>= (skribe-debug) 2) (location? (ast-loc n)))
+		 (printf "<span style=\"display: block; position: relative; left: -2cm; font-size: x-small; font-style: italic; color: ff8e1e;\">~a</span>"
+			 (ast-location n)))
+	      ((html-markup-class "p") n e))
+   :after "</p>")
+
+;*---------------------------------------------------------------------*/
+;*    ~ ...                                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '~
+   :before "&nbsp;"
+   :after #f
+   :action #f)
+
+;*---------------------------------------------------------------------*/
+;*    footnote ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'footnote
+   :options '(:label)
+   :action (lambda (n e)
+	      (printf "<a href=\"#footnote-~a\"><sup><small>~a</small></sup></a>"
+		      (string-canonicalize (container-ident n))
+		      (markup-option n :label))))
+
+;*---------------------------------------------------------------------*/
+;*    linebreak ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'linebreak
+	       :before (lambda (n e)
+			  (display "<br")
+			  (html-class n)
+			  (display "/>")))
+
+;*---------------------------------------------------------------------*/
+;*    hrule ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'hrule
+   :options '(:width :height)
+   :before (lambda (n e)
+	      (let ((width (markup-option n :width))
+		    (height (markup-option n :height)))
+		 (display "<hr")
+		 (html-class n)
+		 (if (< width 100)
+		     (printf " width=\"~a\"" (html-width width)))
+		 (if (> height 1)
+		     (printf " size=\"~a\"" height))
+		 (display ">"))))
+
+;*---------------------------------------------------------------------*/
+;*    color ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'color
+   :options '(:bg :fg :width :margin)
+   :before (lambda (n e)
+	      (let ((m (markup-option n :margin))
+		    (w (markup-option n :width))
+		    (bg (markup-option n :bg))
+		    (fg (markup-option n :fg)))
+		 (when (html-color-spec? bg)
+		    (display "<table cellspacing=\"0\"")
+		    (html-class n)
+		    (printf " cellpadding=\"~a\"" (if m m 0))
+		    (if w (printf " width=\"~a\"" (html-width w)))
+		    (display "><tbody>\n<tr>")
+		    (display "<td bgcolor=\"")
+		    (output bg e)
+		    (display "\">"))
+		 (when (html-color-spec? fg)
+		    (display "<font color=\"")
+		    (output fg e)
+		    (display "\">"))))
+   :after (lambda (n e)
+	     (when (html-color-spec? (markup-option n :fg))
+		(display "</font>"))
+	     (when (html-color-spec? (markup-option n :bg))
+		(display "</td></tr>\n</tbody></table>"))))
+
+;*---------------------------------------------------------------------*/
+;*    frame ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'frame
+   :options '(:width :margin :border)
+   :before (lambda (n e)
+	      (let ((m (markup-option n :margin))
+		    (b (markup-option n :border))
+		    (w (markup-option n :width)))
+		 (display "<table cellspacing=\"0\"")
+		 (html-class n)
+		 (printf " cellpadding=\"~a\"" (if m m 0))
+		 (printf " border=\"~a\"" (if b b 0))
+		 (if w (printf " width=\"~a\"" (html-width w)))
+		 (display "><tbody>\n<tr><td>")))
+   :after "</td></tr>\n</tbody></table>")
+
+;*---------------------------------------------------------------------*/
+;*    font ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'font
+   :options '(:size :face)
+   :before (lambda (n e)
+	      (let ((size (markup-option n :size))
+		    (face (markup-option n :face)))
+		 (when (and (number? size) (inexact? size))
+		    (let ((s (if (> size 0) "<big>" "<small>"))
+			  (d (if (> size 0) 1 -1)))
+		       (do ((i (inexact->exact size) (- i d)))
+			   ((= i 0))
+			   (display s))))
+		 (when (or (and (number? size) (exact? size)) face)
+		    (display "<font")
+		    (html-class n)
+		    (when (and (number? size) (exact? size) (not (= size 0)))
+		       (printf " size=\"~a\"" size))
+		    (when face (printf " face=\"~a\"" face))
+		    (display ">"))))
+   :after (lambda (n e)
+	     (let ((size (markup-option n :size))
+		   (face (markup-option n :face)))
+		(when (or (and (number? size) (exact? size) (not (= size 0)))
+			  face)
+		   (display "</font>"))
+		(when (and (number? size) (inexact? size))
+		   (let ((s (if (> size 0) "</big>" "</small>"))
+			 (d (if (> size 0) 1 -1)))
+		      (do ((i (inexact->exact size) (- i d)))
+			  ((= i 0))
+			  (display s)))))))
+
+;*---------------------------------------------------------------------*/
+;*    flush ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'flush
+   :options '(:side)
+   :before (lambda (n e)
+	      (case (markup-option n :side)
+		 ((center)
+		  (display "<center")
+		  (html-class n)
+		  (display ">\n"))
+		 ((left)
+		  (display "<p style=\"text-align:left;\"")
+		  (html-class n)
+		  (display ">\n"))
+		 ((right)
+		  (display "<table ")
+		  (html-class n)
+		  (display "width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td align=\"right\">"))
+		 (else
+		  (skribe-error 'flush
+				"Illegal side"
+				(markup-option n :side)))))
+   :after (lambda (n e)
+	     (case (markup-option n :side)
+		((center)
+		 (display "</center>\n"))
+		((right)
+		 (display "</td></tr></table>\n"))
+		((left)
+		 (display "</p>\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    center ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'center
+   :before (html-markup-class "center")
+   :after "</center>\n")
+
+;*---------------------------------------------------------------------*/
+;*    pre ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'pre :before (html-markup-class "pre") :after "</pre>\n")
+
+;*---------------------------------------------------------------------*/
+;*    prog ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'prog
+   :options '(:line :mark)
+   :before (html-markup-class "pre")
+   :after "</pre>\n")
+
+;*---------------------------------------------------------------------*/
+;*    itemize ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'itemize
+   :options '(:symbol)
+   :before (html-markup-class "ul")
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			  (let ((ident (and (markup? item)
+					    (markup-ident item))))
+			   (display "<li")
+			   (html-class item)
+			   (display ">")
+			    (if ident  ;; produce an anchor
+				(printf "\n<a name=\"~a\"></a>\n"
+					(string-canonicalize ident)))
+			   (output item e)
+			    (display "</li>\n")))
+			(markup-body n)))
+   :after "</ul>")
+
+;*---------------------------------------------------------------------*/
+;*    enumerate ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'enumerate
+   :options '(:symbol)
+   :before (html-markup-class "ol")
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			  (let ((ident (and (markup? item)
+					    (markup-ident item))))
+			   (display "<li")
+			   (html-class item)
+			   (display ">")
+			    (if ident  ;; produce an anchor
+				(printf "\n<a name=\"~a\"></a>\n" ident))
+			   (output item e)
+			    (display "</li>\n")))
+			(markup-body n)))
+   :after "</ol>")
+
+;*---------------------------------------------------------------------*/
+;*    description ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'description
+   :options '(:symbol)
+   :before (html-markup-class "dl")
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (let ((k (markup-option item :key)))
+			      (for-each (lambda (i)
+					   (display " <dt")
+					   (html-class i)
+					   (display ">")
+					   (output i e)
+					   (display "</dt>"))
+					(if (pair? k) k (list k)))
+			      (display "<dd")
+			      (html-class item)
+			      (display ">")
+			      (output (markup-body item) e)
+			      (display "</dd>\n")))
+			(markup-body n)))
+   :after "</dl>")
+
+;*---------------------------------------------------------------------*/
+;*    item ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'item
+   :options '(:key)
+   :action (lambda (n e)
+	      (let ((k (markup-option n :key)))
+		 (if k
+		     (begin
+			(display "<b")
+			(html-class n)
+			(display ">")
+			(output k e)
+			(display "</b> "))))
+	      (output (markup-body n) e)))
+
+;*---------------------------------------------------------------------*/
+;*    blockquote ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer 'blockquote
+   :options '()
+   :before (lambda (n e)
+	     (display "<blockquote ")
+	     (html-class n)
+	     (display ">\n"))
+   :after "\n</blockquote>\n")
+
+;*---------------------------------------------------------------------*/
+;*    figure ... @label figure@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'figure
+   :options '(:legend :number :multicolumns :legend-width)
+   :before (html-markup-class "br")
+   :action (lambda (n e)
+	      (let ((ident (markup-ident n))
+		    (number (markup-option n :number))
+		    (legend (markup-option n :legend)))
+		 (display "<a name=\"")
+		 (display (string-canonicalize ident))
+		 (display "\"></a>\n")
+		 (output (markup-body n) e)
+		 (display "<br>\n")
+		 (output (new markup
+			    (markup '&html-figure-legend)
+			    (parent n)
+			    (ident (string-append ident "-legend"))
+			    (class (markup-class n))
+			    (options `((:number ,number)))
+			    (body legend))
+			 e)))
+   :after "<br>")
+
+;*---------------------------------------------------------------------*/
+;*    &html-figure-legend ...                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-figure-legend
+   :options '(:number)
+   :before (lambda (n e)
+	      (display "<center>")
+	      (let ((number (markup-option n :number))
+		    (legend (markup-option n :legend)))
+		 (if number
+		     (printf "<strong>Fig. ~a:</strong> " number)
+		     (printf "<strong>Fig. :</strong> "))))
+   :after "</center>")
+
+;*---------------------------------------------------------------------*/
+;*    table ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'table
+   :options '(:border :width :frame :rules :cellstyle :cellpadding :cellspacing)
+   :before (lambda (n e)
+	      (let ((width (markup-option n :width))
+		    (border (markup-option n :border))
+		    (frame (markup-option n :frame))
+		    (rules (markup-option n :rules))
+		    (cstyle (markup-option n :cellstyle))
+		    (cp (markup-option n :cellpadding))
+		    (cs (markup-option n :cellspacing)))
+		 (display "<table")
+		 (html-class n)
+		 (if width (printf " width=\"~a\"" (html-width width)))
+		 (if border (printf " border=\"~a\"" border))
+		 (if (and (number? cp) (>= cp 0))
+		     (printf " cellpadding=\"~a\"" cp))
+		 (if (and (number? cs) (>= cs 0))
+		     (printf " cellspacing=\"~a\"" cs))
+		 (cond
+		    ((symbol? cstyle)
+		     (printf " style=\"border-collapse: ~a;\"" cstyle))
+		    ((string? cstyle)
+		     (printf " style=\"border-collapse: separate; border-spacing=~a\"" cstyle))
+		    ((number? cstyle)
+		     (printf " style=\"border-collapse: separate; border-spacing=~apt\"" cstyle)))
+		 (if frame
+		     (printf " frame=\"~a\""
+			     (if (eq? frame 'none) "void" frame)))
+		 (if (and rules (not (eq? rules 'header)))
+		     (printf " rules=\"~a\"" rules))
+		 (display "><tbody>\n")))
+   :after "</tbody></table>\n")
+
+;*---------------------------------------------------------------------*/
+;*    tr ...                                                           */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tr
+   :options '(:bg)
+   :before (lambda (n e)
+	      (let ((bg (markup-option n :bg)))
+		 (display "<tr")
+		 (html-class n)
+		 (when (html-color-spec? bg) (printf " bgcolor=\"~a\"" bg))
+		 (display ">")))
+   :after "</tr>\n")
+
+;*---------------------------------------------------------------------*/
+;*    tc ...                                                           */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tc
+   :options '(markup :width :align :valign :colspan :rowspan :bg)
+   :before (lambda (n e)
+	      (let ((markup (or (markup-option n 'markup) 'td))
+		    (width (markup-option n :width))
+		    (align (markup-option n :align))
+		    (valign (let ((v (markup-option n :valign)))
+			       (cond
+				  ((or (eq? v 'center)
+				       (equal? v "center"))
+				   "middle")
+				  (else
+				   v))))
+		    (colspan (markup-option n :colspan))
+		    (rowspan (markup-option n :rowspan))
+		    (bg (markup-option n :bg)))
+		 (printf "<~a" markup)
+		 (html-class n)
+		 (if width (printf " width=\"~a\"" (html-width width)))
+		 (if align (printf " align=\"~a\"" align))
+		 (if valign (printf " valign=\"~a\"" valign))
+		 (if colspan (printf " colspan=\"~a\"" colspan))
+		 (if rowspan (printf " rowspan=\"~a\"" rowspan))
+		 (when (html-color-spec? bg)
+		    (printf " bgcolor=\"~a\"" bg))
+		 (display ">")))
+   :after (lambda (n e)
+	     (let ((markup (or (markup-option n 'markup) 'td)))
+		(printf "</~a>" markup))))
+
+;*---------------------------------------------------------------------*/
+;*    image ... @label image@                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'image
+   :options '(:file :url :width :height)
+   :action (lambda (n e)
+	      (let* ((file (markup-option n :file))
+		     (url (markup-option n :url))
+		     (width (markup-option n :width))
+		     (height (markup-option n :height))
+		     (body (markup-body n))
+		     (efmt (engine-custom e 'image-format))
+		     (img (or url (convert-image file
+						 (if (list? efmt)
+						     efmt
+						     '("gif" "jpg" "png"))))))
+		 (if (not (string? img))
+		     (skribe-error 'html "Illegal image" file)
+		     (begin
+			(printf "<img src=\"~a\" border=\"0\"" img)
+			(html-class n)
+			(if body
+			    (begin
+			       (display " alt=\"")
+			       (output body e)
+			       (display "\""))
+			    (printf " alt=\"~a\"" file))
+			(if width (printf " width=\"~a\"" (html-width width)))
+			(if height (printf " height=\"~a\"" height))
+			(display ">"))))))
+
+;*---------------------------------------------------------------------*/
+;*    Ornaments ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'roman :before "")
+(markup-writer 'bold :before (html-markup-class "strong") :after "</strong>")
+(markup-writer 'underline :before (html-markup-class "u") :after "</u>")
+(markup-writer 'strike :before (html-markup-class "strike") :after "</strike>")
+(markup-writer 'emph :before (html-markup-class "em") :after "</em>")
+(markup-writer 'kbd :before (html-markup-class "kbd") :after "</kbd>")
+(markup-writer 'it :before (html-markup-class "em") :after "</em>")
+(markup-writer 'tt :before (html-markup-class "tt") :after "</tt>")
+(markup-writer 'code :before (html-markup-class "code") :after "</code>")
+(markup-writer 'var :before (html-markup-class "var") :after "</var>")
+(markup-writer 'samp :before (html-markup-class "samp") :after "</samp>")
+(markup-writer 'sc :before "<span class=\"sc\">" :after "</span>")
+(markup-writer 'sf :before "<span class=\"sf\">" :after "</span>")
+(markup-writer 'sub :before (html-markup-class "sub") :after "</sub>")
+(markup-writer 'sup :before (html-markup-class "sup") :after "</sup>")
+
+;*---------------------------------------------------------------------*/
+;*    q ... @label q@                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'q
+   :before "\""
+   :after "\"")
+
+;*---------------------------------------------------------------------*/
+;*    mailto ... @label mailto@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mailto
+   :options '(:text)
+   :action (lambda (n e)
+	      (let ((text (markup-option n :text)))
+		 (display "<a href=\"mailto:")
+		 (output (markup-body n) e)
+		 (display #\")
+		 (html-class n)
+		 (display #\>)
+		 (if text
+		     (output text e)
+		     (skribe-eval (tt (markup-body n)) e))
+		 (display "</a>"))))
+
+;*---------------------------------------------------------------------*/
+;*    mailto ... @label mailto@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mailto
+   :options '(:text)
+   :predicate (lambda (n e)
+		 (and (engine-custom e 'javascript)
+		      (or (string? (markup-body n))
+			  (and (pair? (markup-body n))
+			       (null? (cdr (markup-body n)))
+			       (string? (car (markup-body n)))))))
+   :action (lambda (n e)
+	      (let* ((body (markup-body n))
+		     (email (if (string? body) body (car body)))
+		     (split (pregexp-split "@" email))
+		     (na (car split))
+		     (do (if (pair? (cdr split)) (cadr split) ""))
+		     (nn (pregexp-replace* "[.]" na " "))
+		     (dd (pregexp-replace* "[.]" do " "))
+		     (text (markup-option n :text)))
+		(display "<script language=\"JavaScript\" type=\"text/javascript\"")
+		(if (not text)
+		    (printf ">skribenospam( ~s, ~s, true )" nn dd)
+		    (begin
+		      (printf ">skribenospam( ~s, ~s, false )" nn dd)
+		      (display "</script>")
+		      (output text e)
+		      (display "<script language=\"JavaScript\" type=\"text/javascript\">document.write(\"</\" + \"a>\")")))
+		(display "</script>\n"))))
+
+;*---------------------------------------------------------------------*/
+;*    mark ... @label mark@                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mark
+   :before (lambda (n e)
+	      (printf "<a name=\"~a\"" (string-canonicalize (markup-ident n)))
+	      (html-class n)
+	      (display ">"))
+   :after "</a>")
+
+;*---------------------------------------------------------------------*/
+;*    ref ... @label ref@                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer 'ref
+   :options '(:text :chapter :section :subsection :subsubsection :figure :mark :handle)
+   :before (lambda (n e)
+	      (let* ((c (handle-ast (markup-body n)))
+		     (id (markup-ident c))
+		     (f (html-file c e))
+		     (class (if (markup-class n)
+				(markup-class n)
+				"skribilo-ref")))
+		 (printf "<a href=\"~a#~a\" class=\"~a\""
+			 (if (and (*destination-file*)
+				  (string=? f (*destination-file*)))
+			     ""
+			     (strip-ref-base (or f (*destination-file*) "")))
+			 (string-canonicalize id)
+			 class)
+		 (display ">")))
+   :action (lambda (n e)
+	      (let ((t (markup-option n :text))
+		    (m (markup-option n 'mark))
+		    (f (markup-option n :figure))
+		    (c (markup-option n :chapter))
+		    (s (markup-option n :section))
+		    (ss (markup-option n :subsection))
+		    (sss (markup-option n :subsubsection)))
+		 (cond
+		    (t
+		     (output t e))
+		    (f
+		     (output (new markup
+				(markup '&html-figure-ref)
+				(body (markup-body n)))
+			     e))
+		    ((or c s ss sss)
+		     (output (new markup
+				(markup '&html-section-ref)
+				(body (markup-body n)))
+			     e))
+
+		    ((not m)
+		     (output (new markup
+				(markup '&html-unmark-ref)
+				(body (markup-body n)))
+			     e))
+		    (else
+		     (display m)))))
+   :after "</a>")
+
+;*---------------------------------------------------------------------*/
+;*    &html-figure-ref ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-figure-ref
+   :action (lambda (n e)
+	      (let ((c (handle-ast (markup-body n))))
+		 (if (or (not (markup? c))
+			 (not (is-markup? c 'figure)))
+		     (display "???")
+		     (output (markup-option c :number) e)))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-section-ref ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-section-ref
+   :action (lambda (n e)
+	      (let ((c (handle-ast (markup-body n))))
+		 (if (not (markup? c))
+		     (display "???")
+		     (output (markup-option c :title) e)))))
+
+;*---------------------------------------------------------------------*/
+;*    &html-unmark-ref ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&html-unmark-ref
+   :action (lambda (n e)
+	      (let ((c (handle-ast (markup-body n))))
+		 (if (not (markup? c))
+		     (display "???")
+		     (let ((t (markup-option c :title)))
+			(if t
+			    (output t e)
+			    (let ((l (markup-option c :legend)))
+			       (if l
+				   (output t e)
+				   (display
+				    (string-canonicalize
+				     (markup-ident c)))))))))))
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e) (output n e (markup-writer-get 'ref e)))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref+ ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref+
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e)
+	      (let loop ((rs (markup-body n)))
+		 (cond
+		    ((null? rs)
+		     #f)
+		    (else
+		     (if (is-markup? (car rs) 'bib-ref)
+			 (output (car rs) e (markup-writer-get 'ref e))
+			 (output (car rs) e))
+		     (if (pair? (cdr rs))
+			 (begin
+			    (display ",")
+			    (loop (cdr rs))))))))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    url-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'url-ref
+   :options '(:url :text)
+   :before (lambda (n e)
+	      (let* ((url (markup-option n :url))
+		     (class (cond
+			       ((markup-class n)
+				(markup-class n))
+			       ((not (string? url))
+				#f)
+			       (else
+				(let ((l (string-length url)))
+				   (let loop ((i 0))
+				      (cond
+					 ((= i l)
+					  #f)
+					 ((char=? (string-ref url i) #\:)
+					  (substring url 0 i))
+					 (else
+					  (loop (+ i 1))))))))))
+		 (display "<a href=\"")
+		 (output url html-title-engine)
+		 (display "\"")
+		 (when class (printf " class=\"~a\"" class))
+		 (display ">")))
+   :action (lambda (n e)
+	      (let ((v (markup-option n :text)))
+		 (output (or v (markup-option n :url)) e)))
+   :after "</a>")
+
+;*---------------------------------------------------------------------*/
+;*    line-ref ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'line-ref
+   :options '(:offset)
+   :before (html-markup-class "i")
+   :action (lambda (n e)
+	      (let ((o (markup-option n :offset))
+		    (v (string->number (markup-option n :text))))
+		 (if (and (number? o) (number? v))
+		     (markup-option-add! n :text (+ o v)))
+		 (output n e (markup-writer-get 'ref e))
+		 (if (and (number? o) (number? v))
+		     (markup-option-add! n :text v))))
+   :after "</i>")
+
+;*---------------------------------------------------------------------*/
+;*    page-ref ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'page-ref
+   :options '(:mark :handle)
+   :action (lambda (n e)
+	      (error 'page-ref:html "Not implemented yet" n)))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-label ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-label
+   :options '(:title)
+   :before (lambda (n e)
+	      (printf "<a name=\"~a\"" (string-canonicalize (markup-ident n)))
+	      (html-class n)
+	      (display ">"))
+   :action (lambda (n e)
+	      (output n e (markup-writer-get '&bib-entry-label base-engine)))
+   :after "</a>")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-title ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-title
+   :action (lambda (n e)
+	      (let* ((t (bold (markup-body n)))
+		     (en (handle-ast (ast-parent n)))
+		     (url (or (markup-option en 'url)
+			      (markup-option en 'documenturl)))
+		     (ht (if url (ref :url (markup-body url) :text t) t)))
+		 (skribe-eval ht e))))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-url ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-url
+   :action (lambda (n e)
+	      (let* ((en (handle-ast (ast-parent n)))
+		     (url (markup-option en 'url))
+		     (t (bold (markup-body url))))
+		 (skribe-eval (ref :url (markup-body url) :text t) e))))
+
+;*---------------------------------------------------------------------*/
+;*    &the-index-header ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-index-header
+   :action (lambda (n e)
+	      (display "<center")
+	      (html-class n)
+	      (display ">")
+	      (for-each (lambda (h)
+			   (let ((f (engine-custom e 'index-header-font-size)))
+			      (if f
+				  (skribe-eval (font :size f (bold (it h))) e)
+				  (output h e))
+			      (display " ")))
+			(markup-body n))
+	      (display "</center>")
+	      (skribe-eval (linebreak 2) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &source-comment ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (it (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-line-comment ...                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-line-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-keyword ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-keyword
+   :action (lambda (n e)
+	      (skribe-eval (bold (markup-body n)) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &source-error ...                                                */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-error
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-error-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-define ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-define
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-define-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-module ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-module
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-module-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-markup ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-markup
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-markup-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-thread ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-thread
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-thread-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-string ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-string
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-string-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-bracket ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-bracket
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-bracket-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (bold n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-type ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-key ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-key
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-type ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg "red" (bold n1))
+			     (bold n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    Restore the base engine                                          */
+;*---------------------------------------------------------------------*/
+(default-engine-set! (find-engine 'base))
diff --git a/src/guile/skribilo/engine/html4.scm b/src/guile/skribilo/engine/html4.scm
new file mode 100644
index 0000000..48550ef
--- /dev/null
+++ b/src/guile/skribilo/engine/html4.scm
@@ -0,0 +1,168 @@
+;;;;
+;;;; html4.skr				-- HTML 4.01 Engine
+;;;;
+;;;; Copyright © 2004 Erick Gallesio - I3S-CNRS/ESSI <eg@essi.fr>
+;;;;
+;;;;
+;;;; This program is free software; you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation; either version 2 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;;           Author: Erick Gallesio [eg@essi.fr]
+;;;;    Creation date: 18-Feb-2004 11:58 (eg)
+;;;; Last file update: 26-Feb-2004 21:09 (eg)
+;;;;
+
+(define-skribe-module (skribilo engine html4))
+
+(define (find-children node)
+  (define (flat l)
+    (cond
+      ((null? l) l)
+      ((pair? l) (append (flat (car l))
+			 (flat (cdr l))))
+      (else      (list l))))
+
+  (if (markup? node)
+      (flat (markup-body node))
+      node))
+
+;;; ======================================================================
+
+(let ((le (find-engine 'html)))
+  ;;----------------------------------------------------------------------
+  ;;	Customizations
+  ;;----------------------------------------------------------------------
+  (engine-custom-set! le 'html-variant    "html4")
+  (engine-custom-set! le 'html4-logo      "http://www.w3.org/Icons/valid-html401")
+  (engine-custom-set! le 'html4-validator "http://validator.w3.org/check/referer")
+
+  ;;----------------------------------------------------------------------
+  ;;	&html-html ...
+  ;;----------------------------------------------------------------------
+  (markup-writer '&html-html le
+     :before "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+<html>\n"
+     :after "</html>")
+
+  ;;----------------------------------------------------------------------
+  ;;	&html-ending
+  ;;----------------------------------------------------------------------
+  (let* ((img (engine-custom le 'html4-logo))
+	 (url (engine-custom le 'html4-validator))
+	 (bottom (list (hrule)
+		      (table :width 100.
+			 (tr
+			    (td :align 'left
+			       (font :size -1 [
+				 This ,(sc "Html") page has been produced by
+				      ,(ref :url (skribe-url) :text "Skribe").
+				      ,(linebreak)
+				      Last update ,(it (date)).]))
+			    (td :align 'right :valign 'top
+			       (ref :url url
+				  :text (image :url img :width 88 :height 31))))))))
+    (markup-writer '&html-ending le
+       :before "<div class=\"skribe-ending\">"
+       :action (lambda (n e)
+		 (let ((body (markup-body n)))
+		   (if body
+		       (output body #t)
+		       (skribe-eval bottom e))))
+       :after "</div>\n"))
+
+  ;;----------------------------------------------------------------------
+  ;;	color ...
+  ;;----------------------------------------------------------------------
+  (markup-writer 'color le
+     :options '(:bg :fg :width :margin)
+     :before (lambda (n e)
+	       (let ((m    (markup-option n :margin))
+		     (w    (markup-option n :width))
+		     (bg   (markup-option n :bg))
+		     (fg   (markup-option n :fg)))
+		 (when bg
+		   (display "<table cellspacing=\"0\"")
+		   (html-class n)
+		   (printf " cellpadding=\"~a\"" (if m m 0))
+		   (if w (printf " width=\"~a\"" (html-width w)))
+		   (display "><tbody>\n<tr>")
+		   (display "<td bgcolor=\"")
+		   (output bg e)
+		   (display "\">"))
+		 (when fg
+		   (display "<span style=\"color:")
+		   (output fg e)
+		   (display ";\">"))))
+     :after (lambda (n e)
+	      (when (markup-option n :fg)
+		(display "</span>"))
+	      (when (markup-option n :bg)
+		(display "</td></tr>\n</tbody></table>"))))
+
+  ;;----------------------------------------------------------------------
+  ;;	font ...
+  ;;----------------------------------------------------------------------
+  (markup-writer 'font le
+     :options '(:size :face)
+     :before (lambda (n e)
+	       (let ((face (markup-option n :face))
+		     (size (let ((sz (markup-option n :size)))
+			     (cond
+			       ((or (unspecified? sz) (not sz))
+				#f)
+			       ((and (number? sz) (or (inexact? sz) (negative? sz)))
+				(format #f "~a%"
+					(+ 100
+					   (* 20 (inexact->exact (truncate sz))))))
+			       ((number? sz)
+				sz)
+			       (else
+				(skribe-error 'font
+					      (format #f
+						      "illegal font size ~s" sz)
+					      n))))))
+		 (display "<span ")
+		 (html-class n)
+		 (display "style=\"")
+		 (if size (printf "font-size: ~a; " size))
+		 (if face (printf "font-family:'~a'; " face))
+		 (display "\">")))
+     :after "</span>")
+
+  ;;----------------------------------------------------------------------
+  ;;	paragraph ...
+  ;;----------------------------------------------------------------------
+  (copy-markup-writer 'paragraph le
+     :validate (lambda (n e)
+		 (let ((pred (lambda (x)
+			       (and (container? x)
+				    (not (memq (markup-markup x) '(font color)))))))
+		   (not (any pred (find-children n))))))
+
+  ;;----------------------------------------------------------------------
+  ;;	roman ...
+  ;;----------------------------------------------------------------------
+  (markup-writer 'roman le
+     :before "<span style=\"font-family: serif\">"
+     :after "</span>")
+
+  ;;----------------------------------------------------------------------
+  ;;	table ...
+  ;;----------------------------------------------------------------------
+  (let ((old-writer (markup-writer-get 'table le)))
+    (copy-markup-writer 'table le
+	:validate (lambda (n e)
+		    (not (null? (markup-body n))))))
+)
diff --git a/src/guile/skribilo/engine/latex-simple.scm b/src/guile/skribilo/engine/latex-simple.scm
new file mode 100644
index 0000000..638c158
--- /dev/null
+++ b/src/guile/skribilo/engine/latex-simple.scm
@@ -0,0 +1,103 @@
+(define-skribe-module (skribilo engine latex-simple))
+
+;;;
+;;; LES CUSTOMS SONT TROP SPECIFIQUES POUR LA DISTRIBS. NE DOIT PAS VIRER
+;;; CE FICHIER (sion simplifie il ne rest plus grand chose)
+;;;		Erick 27-10-04
+;;;
+
+
+;*=====================================================================*/
+;*    scmws04/src/latex-style.skr                                      */
+;*    -------------------------------------------------------------    */
+;*    Author      :  Damien Ciabrini                                   */
+;*    Creation    :  Tue Aug 24 19:17:04 2004                          */
+;*    Last change :  Thu Oct 28 21:45:25 2004 (eg)                     */
+;*    Copyright   :  2004 Damien Ciabrini, see LICENCE file            */
+;*    -------------------------------------------------------------    */
+;*    Custom style for Latex...                                        */
+;*=====================================================================*/
+
+(let* ((le (find-engine 'latex))
+       (oa (markup-writer-get 'author le)))
+   ; latex class & package for the workshop
+   (engine-custom-set! le 'documentclass "\\documentclass[letterpaper]{sigplan-proc}")
+   (engine-custom-set! le 'usepackage
+   "\\usepackage{epsfig}
+\\usepackage{workshop}
+\\conferenceinfo{Fifth Workshop on Scheme and Functional Programming.}
+	       {September 22, 2004, Snowbird, Utah, USA.}
+\\CopyrightYear{2004}
+\\CopyrightHolder{Damien Ciabrini}
+\\renewcommand{\\ttdefault}{cmtt}
+")
+   (engine-custom-set! le 'image-format '("eps"))
+   (engine-custom-set! le 'source-define-color "#000080")
+   (engine-custom-set! le 'source-thread-color "#8080f0")
+   (engine-custom-set! le 'source-string-color "#000000")
+
+   ; hyperref options
+   (engine-custom-set! le 'hyperref #t)
+   (engine-custom-set! le 'hyperref-usepackage
+   "\\usepackage[bookmarksopen=true, bookmarksopenlevel=2,bookmarksnumbered=true,colorlinks,linkcolor=blue,citecolor=blue,pdftitle={Debugging Scheme Fair Threads}, pdfsubject={debugging cooperative threads based on reactive programming}, pdfkeywords={debugger, functional, reactive programming, Scheme}, pdfauthor={Damien Ciabrini}]{hyperref}")
+   ; nbsp with ~ char
+   (set! latex-encoding (delete! (assoc #\~ latex-encoding) latex-encoding))
+
+   ; let latex process citations
+   (markup-writer 'bib-ref le
+      :options '(:text :bib)
+      :before "\\cite{"
+      :action (lambda (n e) (display (markup-option n :bib)))
+      :after "}")
+   (markup-writer 'bib-ref+ le
+      :options '(:text :bib)
+      :before "\\cite{"
+      :action (lambda (n e)
+		 (let loop ((bibs (markup-option n :bib)))
+		    (if (pair? bibs)
+			(begin
+			   (display (car bibs))
+			   (if (pair? (cdr bibs)) (display ", "))
+			   (loop (cdr bibs))))))
+      :after "}")
+   (markup-writer '&the-bibliography le
+      :action (lambda (n e)
+		 (print "\\bibliographystyle{abbrv}")
+		 (display "\\bibliography{biblio}")))
+
+   ; ACM-style for authors
+   (markup-writer '&latex-author le
+      :before (lambda (n e)
+		 (let ((body (markup-body n)))
+		    (if (pair? body)
+			(print "\\numberofauthors{" (length body) "}"))
+		    (print "\\author{")))
+      :after "}\n")
+   (markup-writer 'author le
+      :options (writer-options oa)
+      :before ""
+      :action (lambda (n e)
+		 (let ((name (markup-option n :name))
+		       (affiliation (markup-option n :affiliation))
+		       (address (markup-option n :address))
+		       (email (markup-option n :email)))
+		    (define (row pre n post)
+		       (display pre)
+		       (output n e)
+		       (display post)
+		       (display "\\\\\n"))
+		    ;; name
+		    (if name (row "\\alignauthor " name ""))
+		    ;; affiliation
+		    (if affiliation (row "\\affaddr{" affiliation "}"))
+		    ;; address
+		    (if (pair? address)
+			(for-each (lambda (x)
+				     (row "\\affaddr{" x "}")) address))
+		    ;; email
+		    (if email (row "\\email{" email "}"))))
+      :after "")
+)
+
+(define (include-biblio)
+   (the-bibliography))
diff --git a/src/guile/skribilo/engine/latex.scm b/src/guile/skribilo/engine/latex.scm
new file mode 100644
index 0000000..8d5b88f
--- /dev/null
+++ b/src/guile/skribilo/engine/latex.scm
@@ -0,0 +1,1784 @@
+;;; latex.scm  --  LaTeX engine.
+;;;
+;;; Copyright 2003, 2004  Manuel Serrano
+;;;
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;; USA.
+
+(define-skribe-module (skribilo engine latex))
+
+;*---------------------------------------------------------------------*/
+;*    latex-verbatim-encoding ...                                      */
+;*---------------------------------------------------------------------*/
+(define latex-verbatim-encoding
+   '((#\\ "{\\char92}")
+     (#\^ "{\\char94}")
+     (#\{ "\\{")
+     (#\} "\\}")
+     (#\& "\\&")
+     (#\$ "\\$")
+     (#\# "\\#")
+     (#\_ "\\_")
+     (#\% "\\%")
+     (#\~ "$_{\\mbox{\\char126}}$")
+     (#\ç "\\c{c}")
+     (#\Ç "\\c{C}")
+     (#\â "\\^{a}")
+     (#\Â "\\^{A}")
+     (#\à "\\`{a}")
+     (#\À "\\`{A}")
+     (#\é "\\'{e}")
+     (#\É "\\'{E}")
+     (#\è "\\`{e}")
+     (#\È "\\`{E}")
+     (#\ê "\\^{e}")
+     (#\Ê "\\^{E}")
+     (#\ù "\\`{u}")
+     (#\Ù "\\`{U}")
+     (#\û "\\^{u}")
+     (#\Û "\\^{U}")
+     (#\ø "{\\o}")
+     (#\ô "\\^{o}")
+     (#\Ô "\\^{O}")
+     (#\ö "\\\"{o}")
+     (#\Ö "\\\"{O}")
+     (#\î "\\^{\\i}")
+     (#\Î "\\^{I}")
+     (#\ï "\\\"{\\i}")
+     (#\Ï "\\\"{I}")
+     (#\] "{\\char93}")
+     (#\[ "{\\char91}")
+     (#\» "\\,{\\tiny{$^{\\gg}$}}")
+     (#\« "{\\tiny{$^{\\ll}$}}\\,")))
+
+;*---------------------------------------------------------------------*/
+;*    latex-encoding ...                                               */
+;*---------------------------------------------------------------------*/
+(define latex-encoding
+   (append '((#\| "$|$")
+	     (#\< "$<$")
+	     (#\> "$>$")
+	     (#\: "{\\char58}")
+	     (#\# "{\\char35}")
+	     (#\Newline " %\n"))
+	   latex-verbatim-encoding))
+		  
+;*---------------------------------------------------------------------*/
+;*    latex-tt-encoding ...                                            */
+;*---------------------------------------------------------------------*/
+(define latex-tt-encoding
+   (append '((#\. ".\\-")
+	     (#\/ "/\\-"))
+	   latex-encoding))
+		  
+;*---------------------------------------------------------------------*/
+;*    latex-pre-encoding ...                                           */
+;*---------------------------------------------------------------------*/
+(define latex-pre-encoding
+   (append '((#\Space "\\ ")
+	     (#\Newline "\\\\\n"))
+	   latex-encoding))
+
+;*---------------------------------------------------------------------*/
+;*    latex-symbol-table ...                                           */
+;*---------------------------------------------------------------------*/
+(define (latex-symbol-table math)
+   `(("iexcl" "!`")
+     ("cent" "c")
+     ("pound" "\\pounds")
+     ("yen" "Y")
+     ("section" "\\S")
+     ("mul" ,(math "^-"))
+     ("copyright" "\\copyright")
+     ("lguillemet" ,(math "\\ll"))
+     ("not" ,(math "\\neg"))
+     ("degree" ,(math "^{\\small{o}}"))
+     ("plusminus" ,(math "\\pm"))
+     ("micro" ,(math "\\mu"))
+     ("paragraph" "\\P")
+     ("middot" ,(math "\\cdot"))
+     ("rguillemet" ,(math "\\gg"))
+     ("1/4" ,(math "\\frac{1}{4}"))
+     ("1/2" ,(math "\\frac{1}{2}"))
+     ("3/4" ,(math "\\frac{3}{4}"))
+     ("iquestion" "?`")
+     ("Agrave" "\\`{A}")
+     ("Aacute" "\\'{A}")
+     ("Acircumflex" "\\^{A}")
+     ("Atilde" "\\~{A}")
+     ("Amul" "\\\"{A}")
+     ("Aring" "{\\AA}")
+     ("AEligature" "{\\AE}")
+     ("Oeligature" "{\\OE}")
+     ("Ccedilla" "{\\c{C}}")
+     ("Egrave" "{\\`{E}}")
+     ("Eacute" "{\\'{E}}")
+     ("Ecircumflex" "{\\^{E}}")
+     ("Euml" "\\\"{E}")
+     ("Igrave" "{\\`{I}}")
+     ("Iacute" "{\\'{I}}")
+     ("Icircumflex" "{\\^{I}}")
+     ("Iuml" "\\\"{I}")
+     ("ETH" "D")
+     ("Ntilde" "\\~{N}")
+     ("Ograve" "\\`{O}")
+     ("Oacute" "\\'{O}")
+     ("Ocurcumflex" "\\^{O}")
+     ("Otilde" "\\~{O}")
+     ("Ouml" "\\\"{O}")
+     ("times" ,(math "\\times"))
+     ("Oslash" "\\O")
+     ("Ugrave" "\\`{U}")
+     ("Uacute" "\\'{U}")
+     ("Ucircumflex" "\\^{U}")
+     ("Uuml" "\\\"{U}")
+     ("Yacute" "\\'{Y}")
+     ("szlig" "\\ss")
+     ("agrave" "\\`{a}")
+     ("aacute" "\\'{a}")
+     ("acircumflex" "\\^{a}")
+     ("atilde" "\\~{a}")
+     ("amul" "\\\"{a}")
+     ("aring" "\\aa")
+     ("aeligature" "\\ae")
+     ("oeligature" "{\\oe}")
+     ("ccedilla" "{\\c{c}}")
+     ("egrave" "{\\`{e}}")
+     ("eacute" "{\\'{e}}")
+     ("ecircumflex" "{\\^{e}}")
+     ("euml" "\\\"{e}")
+     ("igrave" "{\\`{\\i}}")
+     ("iacute" "{\\'{\\i}}")
+     ("icircumflex" "{\\^{\\i}}")
+     ("iuml" "\\\"{\\i}")
+     ("ntilde" "\\~{n}")
+     ("ograve" "\\`{o}")
+     ("oacute" "\\'{o}")
+     ("ocurcumflex" "\\^{o}")
+     ("otilde" "\\~{o}")
+     ("ouml" "\\\"{o}")
+     ("divide" ,(math "\\div"))
+     ("oslash" "\\o")
+     ("ugrave" "\\`{u}")
+     ("uacute" "\\'{u}")
+     ("ucircumflex" "\\^{u}")
+     ("uuml" "\\\"{u}")
+     ("yacute" "\\'{y}")
+     ("ymul" "\\\"{y}")
+     ;; Greek
+     ("Alpha" "A")
+     ("Beta" "B")
+     ("Gamma" ,(math "\\Gamma"))
+     ("Delta" ,(math "\\Delta"))
+     ("Epsilon" "E")
+     ("Zeta" "Z")
+     ("Eta" "H")
+     ("Theta" ,(math "\\Theta"))
+     ("Iota" "I")
+     ("Kappa" "K")
+     ("Lambda" ,(math "\\Lambda"))
+     ("Mu" "M")
+     ("Nu" "N")
+     ("Xi" ,(math "\\Xi"))
+     ("Omicron" "O")
+     ("Pi" ,(math "\\Pi"))
+     ("Rho" "P")
+     ("Sigma" ,(math "\\Sigma"))
+     ("Tau" "T")
+     ("Upsilon" ,(math "\\Upsilon"))
+     ("Phi" ,(math "\\Phi"))
+     ("Chi" "X")
+     ("Psi" ,(math "\\Psi"))
+     ("Omega" ,(math "\\Omega"))
+     ("alpha" ,(math "\\alpha"))
+     ("beta" ,(math "\\beta"))
+     ("gamma" ,(math "\\gamma"))
+     ("delta" ,(math "\\delta"))
+     ("epsilon" ,(math "\\varepsilon"))
+     ("zeta" ,(math "\\zeta"))
+     ("eta" ,(math "\\eta"))
+     ("theta" ,(math "\\theta"))
+     ("iota" ,(math "\\iota"))
+     ("kappa" ,(math "\\kappa"))
+     ("lambda" ,(math "\\lambda"))
+     ("mu" ,(math "\\mu"))
+     ("nu" ,(math "\\nu"))
+     ("xi" ,(math "\\xi"))
+     ("omicron" ,(math "\\o"))
+     ("pi" ,(math "\\pi"))
+     ("rho" ,(math "\\rho"))
+     ("sigmaf" ,(math "\\varsigma"))
+     ("sigma" ,(math "\\sigma"))
+     ("tau" ,(math "\\tau"))
+     ("upsilon" ,(math "\\upsilon"))
+     ("phi" ,(math "\\varphi"))
+     ("chi" ,(math "\\chi"))
+     ("psi" ,(math "\\psi"))
+     ("omega" ,(math "\\omega"))
+     ("thetasym" ,(math "\\vartheta"))
+     ("piv" ,(math "\\varpi"))
+     ;; punctuation
+     ("bullet" ,(math "\\bullet"))
+     ("ellipsis" ,(math "\\ldots"))
+     ("weierp" ,(math "\\wp"))
+     ("image" ,(math "\\Im"))
+     ("real" ,(math "\\Re"))
+     ("tm" ,(math "^{\\sc\\tiny{tm}}"))
+     ("alef" ,(math "\\aleph"))
+     ("<-" ,(math "\\leftarrow"))
+     ("<--" ,(math "\\longleftarrow"))
+     ("uparrow" ,(math "\\uparrow"))
+     ("->" ,(math "\\rightarrow"))
+     ("-->" ,(math "\\longrightarrow"))
+     ("downarrow" ,(math "\\downarrow"))
+     ("<->" ,(math "\\leftrightarrow"))
+     ("<-->" ,(math "\\longleftrightarrow"))
+     ("<+" ,(math "\\hookleftarrow"))
+     ("<=" ,(math "\\Leftarrow"))
+     ("<==" ,(math "\\Longleftarrow"))
+     ("Uparrow" ,(math "\\Uparrow"))
+     ("=>" ,(math "\\Rightarrow"))
+     ("==>" ,(math "\\Longrightarrow"))
+     ("Downarrow" ,(math "\\Downarrow"))
+     ("<=>" ,(math "\\Leftrightarrow"))
+     ("<==>" ,(math "\\Longleftrightarrow"))
+     ;; Mathematical operators
+     ("forall" ,(math "\\forall"))
+     ("partial" ,(math "\\partial"))
+     ("exists" ,(math "\\exists"))
+     ("emptyset" ,(math "\\emptyset"))
+     ("infinity" ,(math "\\infty"))
+     ("nabla" ,(math "\\nabla"))
+     ("in" ,(math "\\in"))
+     ("notin" ,(math "\\notin"))
+     ("ni" ,(math "\\ni"))
+     ("prod" ,(math "\\Pi"))
+     ("sum" ,(math "\\Sigma"))
+     ("asterisk" ,(math "\\ast"))
+     ("sqrt" ,(math "\\surd"))
+     ("propto" ,(math "\\propto"))
+     ("angle" ,(math "\\angle"))
+     ("and" ,(math "\\wedge"))
+     ("or" ,(math "\\vee"))
+     ("cap" ,(math "\\cap"))
+     ("cup" ,(math "\\cup"))
+     ("integral" ,(math "\\int"))
+     ("models" ,(math "\\models"))
+     ("vdash" ,(math "\\vdash"))
+     ("dashv" ,(math "\\dashv"))
+     ("sim" ,(math "\\sim"))
+     ("cong" ,(math "\\cong"))
+     ("approx" ,(math "\\approx"))
+     ("neq" ,(math "\\neq"))
+     ("equiv" ,(math "\\equiv"))
+     ("le" ,(math "\\leq"))
+     ("ge" ,(math "\\geq"))
+     ("subset" ,(math "\\subset"))
+     ("supset" ,(math "\\supset"))
+     ("subseteq" ,(math "\\subseteq"))
+     ("supseteq" ,(math "\\supseteq"))
+     ("oplus" ,(math "\\oplus"))
+     ("otimes" ,(math "\\otimes"))
+     ("perp" ,(math "\\perp"))
+     ("mid" ,(math "\\mid"))
+     ("lceil" ,(math "\\lceil"))
+     ("rceil" ,(math "\\rceil"))
+     ("lfloor" ,(math "\\lfloor"))
+     ("rfloor" ,(math "\\rfloor"))
+     ("langle" ,(math "\\langle"))
+     ("rangle" ,(math "\\rangle"))
+     ;; Misc
+     ("loz" ,(math "\\diamond"))
+     ("spades" ,(math "\\spadesuit"))
+     ("clubs" ,(math "\\clubsuit"))
+     ("hearts" ,(math "\\heartsuit"))
+     ("diams" ,(math "\\diamondsuit"))
+     ("euro" "\\euro{}")
+     ;; LaTeX
+     ("dag" "\\dag")
+     ("ddag" "\\ddag")
+     ("circ" ,(math "\\circ"))
+     ("top" ,(math "\\top"))
+     ("bottom" ,(math "\\bot"))
+     ("lhd" ,(math "\\triangleleft"))
+     ("rhd" ,(math "\\triangleright"))
+     ("parallel" ,(math "\\parallel"))))
+
+;*---------------------------------------------------------------------*/
+;*    latex-engine ...                                                 */
+;*---------------------------------------------------------------------*/
+(define latex-engine
+   (default-engine-set!
+      (make-engine 'latex
+	 :version 1.0
+	 :format "latex"
+	 :delegate (find-engine 'base)
+	 :filter (make-string-replace latex-encoding)
+	 :custom '((documentclass "\\documentclass{article}")
+		   (usepackage "\\usepackage{epsfig}\n")
+		   (predocument "\\newdimen\\oldframetabcolsep\n\\newdimen\\oldcolortabcolsep\n\\newdimen\\oldpretabcolsep\n")
+		   (postdocument #f)
+		   (maketitle "\\date{}\n\\maketitle")
+		   (%font-size 0)
+		   ;; color
+		   (color #t)
+		   (color-usepackage "\\usepackage{color}\n")
+		   ;; hyperref
+		   (hyperref #t)
+		   (hyperref-usepackage "\\usepackage[setpagesize=false]{hyperref}\n")
+		   ;; source fontification
+		   (source-color #t)
+		   (source-comment-color "#ffa600")
+		   (source-error-color "red")
+		   (source-define-color "#6959cf")
+		   (source-module-color "#1919af")
+		   (source-markup-color "#1919af")
+		   (source-thread-color "#ad4386")
+		   (source-string-color "red")
+		   (source-bracket-color "red")
+		   (source-type-color "#00cf00")
+		   (image-format ("eps"))
+		   (index-page-ref #t))
+	 :symbol-table (latex-symbol-table 
+			(lambda (m)
+			   (format #f "\\begin{math}~a\\end{math}" m))))))
+
+;*---------------------------------------------------------------------*/
+;*    latex-title-engine ...                                           */
+;*---------------------------------------------------------------------*/
+(define latex-title-engine
+   (make-engine 'latex-title
+      :version 1.0
+      :format "latex-title"
+      :delegate latex-engine
+      :filter (make-string-replace latex-encoding)
+      :symbol-table (latex-symbol-table (lambda (m) (format #f "$~a$" m)))))
+
+;*---------------------------------------------------------------------*/
+;*    latex-color? ...                                                 */
+;*---------------------------------------------------------------------*/
+(define (latex-color? e)
+   (engine-custom e 'color))
+
+;*---------------------------------------------------------------------*/
+;*    LaTeX ...                                                        */
+;*---------------------------------------------------------------------*/
+(define-markup (LaTeX #!key (space #t))
+   (if (engine-format? "latex")
+       (! (if space "\\LaTeX\\ " "\\LaTeX"))
+       "LaTeX"))
+
+;*---------------------------------------------------------------------*/
+;*    TeX ...                                                          */
+;*---------------------------------------------------------------------*/
+(define-markup (TeX #!key (space #t))
+   (if (engine-format? "latex")
+       (! (if space "\\TeX\\ " "\\TeX"))
+       "TeX"))
+
+;*---------------------------------------------------------------------*/
+;*    latex ...                                                        */
+;*---------------------------------------------------------------------*/
+(define-markup (!latex fmt #!rest opt)
+   (if (engine-format? "latex")
+       (apply ! fmt opt)
+       #f))
+   
+;*---------------------------------------------------------------------*/
+;*    latex-width ...                                                  */
+;*---------------------------------------------------------------------*/
+(define (latex-width width)
+   (if (and (number? width) (inexact? width))
+       (string-append (number->string (/ width 100.)) "\\linewidth")
+       (string-append (number->string width) "pt")))
+
+;*---------------------------------------------------------------------*/
+;*    latex-font-size ...                                              */
+;*---------------------------------------------------------------------*/
+(define (latex-font-size size)
+   (case size
+      ((4) "Huge")
+      ((3) "huge")
+      ((2) "Large")
+      ((1) "large")
+      ((0) "normalsize")
+      ((-1) "small")
+      ((-2) "footnotesize")
+      ((-3) "scriptsize")
+      ((-4) "tiny")
+      (else (if (number? size)
+		(if (< size 0) "tiny" "Huge")
+		"normalsize"))))
+
+;*---------------------------------------------------------------------*/
+;*    *skribe-latex-color-table* ...                                   */
+;*---------------------------------------------------------------------*/
+(define *skribe-latex-color-table* #f)
+
+;*---------------------------------------------------------------------*/
+;*    latex-declare-color ...                                          */
+;*---------------------------------------------------------------------*/
+(define (latex-declare-color name rgb)
+   (printf "\\definecolor{~a}{rgb}{~a}\n" name rgb))
+
+;*---------------------------------------------------------------------*/
+;*    skribe-get-latex-color ...                                       */
+;*---------------------------------------------------------------------*/
+(define (skribe-get-latex-color spec)
+   (let ((c (and (hashtable? *skribe-latex-color-table*)
+		 (hashtable-get *skribe-latex-color-table* spec))))
+      (if (not (string? c))
+	  (skribe-error 'latex "Can't find color" spec)
+	  c)))
+
+;*---------------------------------------------------------------------*/
+;*    skribe-color->latex-rgb ...                                      */
+;*---------------------------------------------------------------------*/
+(define (skribe-color->latex-rgb spec)
+   (receive (r g b)
+      (skribe-color->rgb spec)
+      (cond
+	 ((and (= r 0) (= g 0) (= b 0))
+	  "0.,0.,0.")
+	 ((and (= r #xff) (= g #xff) (= b #xff))
+	  "1.,1.,1.")
+	 (else
+	  (let ((ff (exact->inexact #xff)))
+	    (format #f "~a,~a,~a"
+		    (number->string (/ r ff))
+		    (number->string (/ g ff))
+		    (number->string (/ b ff))))))))
+
+;*---------------------------------------------------------------------*/
+;*    skribe-latex-declare-colors ...                                  */
+;*---------------------------------------------------------------------*/
+(define (skribe-latex-declare-colors colors)
+   (set! *skribe-latex-color-table* (make-hashtable))
+   (for-each (lambda (spec)
+		(let ((old (hashtable-get *skribe-latex-color-table* spec)))
+		   (if (not (string? old))
+		       (let ((name (symbol->string (gensym 'c))))
+			  ;; bind the color 
+			  (hashtable-put! *skribe-latex-color-table* spec name)
+			  ;; and emit a latex declaration
+			  (latex-declare-color 
+			   name 
+			   (skribe-color->latex-rgb spec))))))
+	     colors))
+
+;*---------------------------------------------------------------------*/
+;*    ~ ...                                                           */
+;*---------------------------------------------------------------------*/
+(markup-writer '~
+   :before "~"
+   :action #f)
+
+;*---------------------------------------------------------------------*/
+;*    &latex-table-start                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-table-start
+   :options '()
+   :action (lambda (n e)
+	      (let ((width (markup-option n 'width)))
+		 (if (number? width)
+		     (printf "\\begin{tabular*}{~a}" (latex-width width))
+		     (display "\\begin{tabular}")))))
+
+;*---------------------------------------------------------------------*/
+;*    &latex-table-stop                                                */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-table-stop
+   :options '()
+   :action (lambda (n e)
+	      (let ((width (markup-option n 'width)))
+		 (if (number? width)
+		 (display "\\end{tabular*}\n")
+		 (display "\\end{tabular}\n")))))
+   
+;*---------------------------------------------------------------------*/
+;*    document ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'document
+   :options '(:title :author :ending :env)
+   :before (lambda (n e)
+	      ;; documentclass
+	      (let ((dc (engine-custom e 'documentclass)))
+		 (if dc
+		     (begin (display dc) (newline))
+		     (display "\\documentclass{article}\n")))
+	      (if (latex-color? e)
+		  (display (engine-custom e 'color-usepackage)))
+	      (if (engine-custom e 'hyperref)
+		  (display (engine-custom e 'hyperref-usepackage)))
+	      ;; usepackage
+	      (let ((pa (engine-custom e 'usepackage)))
+		 (if pa (begin (display pa) (newline))))
+	      ;; colors
+	      (if (latex-color? e)
+		  (begin
+		     (skribe-use-color! (engine-custom e 'source-comment-color))
+		     (skribe-use-color! (engine-custom e 'source-define-color))
+		     (skribe-use-color! (engine-custom e 'source-module-color))
+		     (skribe-use-color! (engine-custom e 'source-markup-color))
+		     (skribe-use-color! (engine-custom e 'source-thread-color))
+		     (skribe-use-color! (engine-custom e 'source-string-color))
+		     (skribe-use-color! (engine-custom e 'source-bracket-color))
+		     (skribe-use-color! (engine-custom e 'source-type-color))
+		     (display "\n%% colors\n")
+		     (skribe-latex-declare-colors (skribe-get-used-colors))
+		     (display "\n\n")))
+	      ;; predocument
+	      (let ((pd (engine-custom e 'predocument)))
+		 (when pd (display pd) (newline)))
+	      ;; title
+	      (let ((t (markup-option n :title)))
+		 (when t
+		    (skribe-eval (new markup
+				    (markup '&latex-title)
+				    (body t))
+				 e
+				 :env `((parent ,n)))))
+	      ;; author
+	      (let ((a (markup-option n :author)))
+		 (when a
+		    (skribe-eval (new markup
+				    (markup '&latex-author)
+				    (body a))
+				 e
+				 :env `((parent ,n)))))
+	      ;; document
+	      (display "\\begin{document}\n")
+	      ;; postdocument
+	      (let ((pd (engine-custom e 'postdocument)))
+		 (if pd (begin (display pd) (newline))))
+	      ;; maketitle
+	      (let ((mt (engine-custom e 'maketitle)))
+		 (if mt (begin (display mt) (newline)))))
+   :action (lambda (n e)
+	      (output (markup-body n) e))
+   :after (lambda (n e)
+	     (display "\n\\end{document}\n")))
+
+;*---------------------------------------------------------------------*/
+;*    &latex-title ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-title
+   :before "\\title{"
+   :after "}\n")
+
+;*---------------------------------------------------------------------*/
+;*    &latex-author ...                                                */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-author
+   :before "\\author{\\centerline{\n"
+   :action (lambda (n e)
+	      (let ((body (markup-body n)))
+		 (if (pair? body)
+		     (begin
+			(output (new markup
+				   (markup '&latex-table-start)
+				   (class "&latex-author-table"))
+				e)
+			(printf "{~a}\n" (make-string (length body) #\c))
+			(let loop ((as body))
+			   (output (car as) e)
+			   (if (pair? (cdr as))
+			       (begin
+				  (display " & ")
+				  (loop (cdr as)))))
+			(display "\\\\\n")
+			(output (new markup
+				   (markup '&latex-table-stop)
+				   (class "&latex-author-table"))
+				e))
+		     (output body e))))
+   :after "}}\n")
+		 
+;*---------------------------------------------------------------------*/
+;*    author ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address :phone :photo :align)
+   :before (lambda (n e)
+	      (output (new markup
+			 (markup '&latex-table-start)
+			 (class "author"))
+		      e)
+	      (printf "{~a}\n"
+		      (case (markup-option n :align)
+			 ((left) "l")
+			 ((right) "r")
+			 (else "c"))))
+   :action (lambda (n e)
+	      (let ((name (markup-option n :name))
+		    (title (markup-option n :title))
+		    (affiliation (markup-option n :affiliation))
+		    (email (markup-option n :email))
+		    (url (markup-option n :url))
+		    (address (markup-option n :address))
+		    (phone (markup-option n :phone)))
+		 (define (row n)
+		    (output n e)
+		    (display "\\\\\n"))
+		 ;; name
+		 (if name (row name))
+		 ;; title
+		 (if title (row title))
+		 ;; affiliation
+		 (if affiliation (row affiliation))
+		 ;; address
+		 (cond
+		    ((pair? address)
+		     (for-each row address))
+		    ((string? address)
+		     (row address)))
+		 ;; telephone
+		 (if phone (row phone))
+		 ;; email
+		 (if email (row email))
+		 ;; url
+		 (if url (row url))))
+   :after (lambda (n e)
+	     (output (new markup
+			(markup '&latex-table-stop)
+			(class "author"))
+		     e)))
+
+;*---------------------------------------------------------------------*/
+;*    author ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address :phone :photo :align)
+   :predicate (lambda (n e) (markup-option n :photo))
+   :before (lambda (n e)
+	      (output (new markup
+			 (markup '&latex-table-start)
+			 (class "author"))
+		      e)
+	      (printf "{cc}\n"))
+   :action (lambda (n e)
+	      (let ((photo (markup-option n :photo)))
+		 (output photo e)
+		 (display " & ")
+		 (markup-option-add! n :photo #f)
+		 (output n e)
+		 (markup-option-add! n :photo photo)
+		 (display "\\\\\n")))
+   :after (lambda (n e)
+	     (output (new markup
+			(markup '&latex-table-stop)
+			(class "author"))
+		     e)))
+
+;*---------------------------------------------------------------------*/
+;*    toc ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'toc
+   :options '()
+   :action (lambda (n e) (display "\\tableofcontents\n")))
+
+;*---------------------------------------------------------------------*/
+;*    latex-block-before ...                                           */
+;*---------------------------------------------------------------------*/
+(define (latex-block-before m)
+   (lambda (n e)
+      (let ((num (markup-option n :number)))
+	 (printf "\n\n%% ~a\n" (string-canonicalize (markup-ident n)))
+	 (printf "\\~a~a{" m (if (not num) "*" ""))
+	 (output (markup-option n :title) latex-title-engine)
+	 (display "}\n")
+	 (when num
+	    (printf "\\label{~a}\n" (string-canonicalize (markup-ident n)))))))
+
+;*---------------------------------------------------------------------*/
+;*    section ... .. @label chapter@                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer 'chapter
+   :options '(:title :number :toc :file :env)
+   :before (latex-block-before 'chapter))
+
+;*---------------------------------------------------------------------*/
+;*    section ... . @label section@                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'section
+   :options '(:title :number :toc :file :env)
+   :before (latex-block-before 'section))
+
+;*---------------------------------------------------------------------*/
+;*    subsection ... @label subsection@                                */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsection
+   :options '(:title :number :toc :file :env)
+   :before (latex-block-before 'subsection))
+
+;*---------------------------------------------------------------------*/
+;*    subsubsection ... @label subsubsection@                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsubsection
+   :options '(:title :number :toc :file :env)
+   :before (latex-block-before 'subsubsection))
+
+;*---------------------------------------------------------------------*/
+;*    paragraph ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'paragraph
+   :options '(:title :number :toc :env)
+   :before (lambda (n e)
+	      (when (and (>= (skribe-debug) 2) (location? (ast-loc n)))
+		 (printf "\n\\makebox[\\linewidth][l]{\\hspace{-1.5cm}\\footnotesize{$\\triangleright$\\textit{~a}}}\n" 
+			 (ast-location n)))
+	      (display "\\noindent "))
+   :after "\\par\n")
+
+;*---------------------------------------------------------------------*/
+;*    footnote ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'footnote
+   :before "\\footnote{"
+   :after "}")
+
+;*---------------------------------------------------------------------*/
+;*    linebreak ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'linebreak
+   :action (lambda (n e)
+	      (display "\\makebox[\\linewidth]{}")))
+
+;*---------------------------------------------------------------------*/
+;*    hrule ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'hrule 
+   :options '()
+   :before "\\hrulefill"
+   :action #f)
+
+;*---------------------------------------------------------------------*/
+;*    latex-color-counter                                              */
+;*---------------------------------------------------------------------*/
+(define latex-color-counter 1)
+
+;*---------------------------------------------------------------------*/
+;*    latex-color ...                                                  */
+;*---------------------------------------------------------------------*/
+(define latex-color 
+   (lambda (bg fg n e)
+      (if (not (latex-color? e))
+	  (output n e)
+	  (begin
+	     (if bg
+		 (printf "\\setbox~a \\vbox \\bgroup " latex-color-counter))
+	     (set! latex-color-counter (+ latex-color-counter 1))
+	     (if fg
+		 (begin
+		    (printf "\\textcolor{~a}{" (skribe-get-latex-color fg))
+		    (output n e)
+		    (display "}"))
+		 (output n e))
+	     (set! latex-color-counter (- latex-color-counter 1))
+	     (if bg
+		 (printf "\\egroup\\colorbox{~a}{\\box~a}%\n"
+			 (skribe-get-latex-color bg) latex-color-counter))))))
+   
+;*---------------------------------------------------------------------*/
+;*    color ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'color
+   :options '(:bg :fg :width)
+   :action (lambda (n e) 
+	      (let* ((w (markup-option n :width))
+		     (bg (markup-option n :bg))
+		     (fg (markup-option n :fg))
+		     (m (markup-option n :margin))
+		     (tw (cond
+			    ((not w)
+			     #f)
+			    ((and (integer? w) (exact? w))
+			     w)
+			    ((real? w)
+			     (latex-width w)))))
+		 (when bg 
+		    (display "\\setlength{\\oldcolortabcolsep}{\\tabcolsep}\n\\addtolength{\\tabcolsep}{-\\tabcolsep}\n")
+		    (when m
+		       (printf "\\addtolength{\\tabcolsep}{~a}" 
+			       (latex-width m)))
+		    (output (new markup
+			       (markup '&latex-table-start)
+			       (class "color"))
+			    e)
+		    (if tw
+			(printf "{p{~a}}\n" tw)
+			(printf "{l}\n")))
+		 (latex-color bg fg (markup-body n) e)
+		 (when bg 
+		    (output (new markup
+			       (markup '&latex-table-stop)
+			       (class "color"))
+			    e)
+		    (display "\\setlength{\\tabcolsep}{\\oldcolortabcolsep}\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    frame ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'frame
+   :options '(:width :border :margin)
+   :before (lambda (n e)
+	      (display "\\setlength{\\oldframetabcolsep}{\\tabcolsep}\n\\addtolength{\\tabcolsep}{-\\tabcolsep}")
+	      (let ((m (markup-option n :margin)))
+		 (when m
+		    (printf "\\addtolength{\\tabcolsep}{~a}" (latex-width m))))
+	      (newline))
+   :action (lambda (n e) 
+	      (let* ((b (markup-option n :border))
+		     (w (markup-option n :width))
+		     (tw (cond
+			    ((not w)
+			     ".96\\linewidth")
+			    ((and (integer? w) (exact? w))
+			     w)
+			    ((real? w)
+			     (latex-width w)))))
+		 (output (new markup
+			    (markup '&latex-table-start)
+			    (class "frame"))
+			 e)
+		 (if (and (integer? b) (> b 0))
+		     (begin
+			(printf "{|p{~a}|}\\hline\n" tw)
+			(output (markup-body n) e)
+			(display "\\\\\\hline\n"))
+		     (begin
+			(printf "{p{~a}}\n" tw)
+			(output (markup-body n) e)))
+		 (output (new markup
+			    (markup '&latex-table-stop)
+			    (class "author"))
+			 e)))
+   :after "\\setlength{\\tabcolsep}{\\oldframetabcolsep}\n")
+
+;*---------------------------------------------------------------------*/
+;*    font ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'font
+   :options '(:size)
+   :action (lambda (n e) 
+	      (let* ((size (markup-option n :size))
+		     (cs (let ((n (engine-custom e '%font-size)))
+			    (if (number? n)
+				n 
+				0)))
+		     (ns (cond
+			    ((and (integer? size) (exact? size))
+			     (if (> size 0)
+				 size
+				 (+ cs size)))
+			    ((and (number? size) (inexact? size))
+			     (+ cs (inexact->exact size)))
+			    ((string? size)
+			     (let ((nb (string->number size)))
+				(if (not (number? nb))
+				    (skribe-error 
+				     'font
+				     (format #f "Illegal font size ~s" size)
+				     nb)
+				    (+ cs nb))))))
+		     (ne (make-engine (gensym 'latex)
+				      :delegate e
+				      :filter (engine-filter e)
+				      :symbol-table (engine-symbol-table e)
+				      :custom `((%font-size ,ns)
+						,@(engine-customs e)))))
+		 (printf "{\\~a{" (latex-font-size ns))
+		 (output (markup-body n) ne)
+		 (display "}}"))))
+
+;*---------------------------------------------------------------------*/
+;*    flush ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'flush
+   :options '(:side)
+   :before (lambda (n e)
+	      (case (markup-option n :side)
+		 ((center)
+		  (display "\\begin{center}\n"))
+		 ((left)
+		  (display "\\begin{flushleft}"))
+		 ((right)
+		  (display "\\begin{flushright}"))))
+   :after (lambda (n e)
+	     (case (markup-option n :side)
+		((center)
+		 (display "\\end{center}\n"))
+		((left)
+		 (display "\\end{flushleft}\n"))
+		((right)
+		 (display "\\end{flushright}\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    center ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'center
+   :before "\\begin{center}\n"
+   :after "\\end{center}\n")
+
+;*---------------------------------------------------------------------*/
+;*    pre ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'pre
+   :before (lambda (n e)
+	      (printf "\\setlength{\\oldpretabcolsep}{\\tabcolsep}\n\\addtolength{\\tabcolsep}{-\\tabcolsep}\n{\\setbox~a \\vbox \n\\bgroup\n{\\noindent \\texttt{"
+		      latex-color-counter)
+	      (output (new markup
+			 (markup '&latex-table-start)
+			 (class "pre"))
+		      e)
+	      (display "{l}\n")
+	      (set! latex-color-counter (+ latex-color-counter 1)))
+   :action (lambda (n e)
+	      (let ((ne (make-engine
+			   (gensym 'latex)
+			   :delegate e
+			   :filter (make-string-replace latex-pre-encoding)
+			   :symbol-table (engine-symbol-table e)
+			   :custom (engine-customs e))))
+		 (output (markup-body n) ne)))
+   :after (lambda (n e)
+	     (set! latex-color-counter (- latex-color-counter 1))
+	     (output (new markup
+			(markup '&latex-table-stop)
+			(class "pre"))
+		     e)
+	     (printf "}}\n\\egroup{\\box~a}}%\n\\setlength{\\tabcolsep}{\\oldpretabcolsep}\n" latex-color-counter)))
+
+;*---------------------------------------------------------------------*/
+;*    prog ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'prog
+   :options '(:line :mark)
+   :before (lambda (n e)
+	      (printf "\\setlength{\\oldpretabcolsep}{\\tabcolsep}\n\\addtolength{\\tabcolsep}{-\\tabcolsep}\n{\\setbox~a \\vbox \\bgroup\n{\\noindent \\texttt{"
+		      latex-color-counter)
+	      (output (new markup
+			 (markup '&latex-table-start)
+			 (class "pre"))
+		      e)
+	      (display "{l}\n")
+	      (set! latex-color-counter (+ latex-color-counter 1)))
+   :action (lambda (n e)
+	      (let ((ne (make-engine
+			   (gensym 'latex)
+			   :delegate e
+			   :filter (make-string-replace latex-pre-encoding)
+			   :symbol-table (engine-symbol-table e)
+			   :custom (engine-customs e))))
+		 (output (markup-body n) ne)))
+   :after (lambda (n e)
+	     (set! latex-color-counter (- latex-color-counter 1))
+	     (output (new markup
+			(markup '&latex-table-stop)
+			(class "prog"))
+		     e)
+	     (printf "}}\n\\egroup{\\box~a}}%\n\\setlength{\\tabcolsep}{\\oldpretabcolsep}\n" latex-color-counter)))
+
+;*---------------------------------------------------------------------*/
+;*    &prog-line ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&prog-line
+   :before (lambda (n e)
+	      (let ((n (markup-ident n)))
+		 (if n (skribe-eval (it (list n) ": ") e))))
+   :after "\\\\\n")
+
+;*---------------------------------------------------------------------*/
+;*    itemize ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'itemize
+   :options '(:symbol)	       
+   :before "\\begin{itemize}\n"
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (display " \\item ")
+			   (output item e)
+			   (newline))
+			(markup-body n)))
+   :after "\\end{itemize} ")
+
+(markup-writer 'itemize
+   :predicate (lambda (n e) (markup-option n :symbol))
+   :options '(:symbol)	       
+   :before (lambda (n e)
+	      (display "\\begin{list}{")
+	      (output (markup-option n :symbol) e)
+	      (display "}{}")
+	      (newline))
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (display " \\item ")
+			   (output item e)
+			   (newline))
+			(markup-body n)))
+   :after "\\end{list}\n")
+
+;*---------------------------------------------------------------------*/
+;*    enumerate ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'enumerate
+   :options '(:symbol)	       
+   :before "\\begin{enumerate}\n"
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (display " \\item ")
+			   (output item e)
+			   (newline))
+			(markup-body n)))
+   :after "\\end{enumerate}\n")
+
+;*---------------------------------------------------------------------*/
+;*    description ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'description
+   :options '(:symbol)	       
+   :before "\\begin{description}\n"
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (let ((k (markup-option item :key)))
+			      (for-each (lambda (i)
+					   (display " \\item[")
+					   (output i e)
+					   (display "]\n"))
+					(if (pair? k) k (list k)))
+			      (output (markup-body item) e)))
+			(markup-body n)))
+   :after "\\end{description}\n")
+
+;*---------------------------------------------------------------------*/
+;*    item ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'item
+   :options '(:key)	       
+   :action (lambda (n e)
+	      (let ((k (markup-option n :key)))
+		 (if k
+		     (begin
+			(display "[")
+			(output k e)
+			(display "] "))))
+	      (output (markup-body n) e)))
+
+;*---------------------------------------------------------------------*/
+;*    blockquote ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer 'blockquote
+   :before "\n\\begin{quote}\n"
+   :after  "\n\\end{quote}")
+
+;*---------------------------------------------------------------------*/
+;*    figure ... @label figure@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'figure
+   :options '(:legend :number :multicolumns)
+   :action (lambda (n e)
+	      (let ((ident (markup-ident n))
+		    (number (markup-option n :number))
+		    (legend (markup-option n :legend))
+		    (mc (markup-option n :multicolumns)))
+		 (display (if mc
+			      "\\begin{figure*}[!th]\n"
+			      "\\begin{figure}[ht]\n"))
+		 (output (markup-body n) e)
+		 (printf "\\caption{\\label{~a}" (string-canonicalize ident))
+		 (output legend e)
+		 (display (if mc
+			      "}\\end{figure*}\n"
+			      "}\\end{figure}\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    table-column-number ...                                          */
+;*    -------------------------------------------------------------    */
+;*    Computes how many columns are contained in a table.              */
+;*---------------------------------------------------------------------*/
+(define (table-column-number t)
+   (define (row-columns row)
+      (let luup ((cells (markup-body row))
+		 (nbcols 0))
+	 (cond
+	   ((null? cells)
+	     nbcols)
+	   ((pair? cells)
+	    (luup (cdr cells)
+		  (+ nbcols (markup-option (car cells) :colspan))))
+	   (else
+	    (skribe-type-error 'tr "Illegal tr body, " row "pair")))))
+   (let loop ((rows (markup-body t))
+	      (nbcols 0))
+      (if (null? rows)
+	  nbcols
+	  (loop (cdr rows)
+		(max (row-columns (car rows)) nbcols)))))
+
+;*---------------------------------------------------------------------*/
+;*    table ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'table
+   :options '(:width :frame :rules :cellstyle)
+   :before (lambda (n e)
+	      (let ((width (markup-option n :width))
+		    (frame (markup-option n :frame))
+		    (rules (markup-option n :rules))
+		    (cstyle (markup-option n :cellstyle))
+		    (nbcols (table-column-number n))
+		    (id (markup-ident n))
+		    (cla (markup-class n))
+		    (rows (markup-body n)))
+		 ;; the table header
+		 (output (new markup
+			    (markup '&latex-table-start)
+			    (class "table")
+			    (options `((width ,width))))
+			 e)
+		 ;; store the actual number of columns
+		 (markup-option-add! n '&nbcols nbcols)
+		 ;; compute the table header
+		 (let ((cols (cond
+				((= nbcols 0)
+				 (skribe-error 'table
+					       "Illegal empty table"
+					       n))
+				((or (not width) (= nbcols 1))
+				 (make-string nbcols #\c))
+				(else
+				 (let ((v (make-vector 
+					   (- nbcols 1)
+					   "@{\\extracolsep{\\fill}}c")))
+				    (apply string-append
+					   (cons "c" (vector->list v))))))))
+		    (case frame
+		       ((none)
+			(printf "{~a}\n" cols))
+		       ((border box)
+			(printf "{|~a|}" cols)
+			(markup-option-add! n '&lhs #t)
+			(markup-option-add! n '&rhs #t)
+			(output (new markup
+				   (markup '&latex-table-hline)
+				   (parent n)
+				   (ident (format #f "~a-above" id))
+				   (class "table-line-above"))
+				e))
+		       ((above hsides)
+			(printf "{~a}" cols)
+			(output (new markup
+				   (markup '&latex-table-hline)
+				   (parent n)
+				   (ident (format #f "~a-above" id))
+				   (class "table-line-above"))
+				e))
+		       ((vsides)
+			(markup-option-add! n '&lhs #t)
+			(markup-option-add! n '&rhs #t)
+			(printf "{|~a|}\n" cols))
+		       ((lhs)
+			(markup-option-add! n '&lhs #t)
+			(printf "{|~a}\n" cols))
+		       ((rhs)
+			(markup-option-add! n '&rhs #t)
+			(printf "{~a|}\n" cols))
+		       (else
+			(printf "{~a}\n" cols)))
+		    ;; mark each row with appropriate '&tl (top-line)
+		    ;; and &bl (bottom-line) options
+		    (when (pair? rows)
+		       (if (and (memq rules '(rows all))
+				(or (not (eq? cstyle 'collapse))
+				    (not (memq frame '(border box above hsides)))))
+			   (let ((frow (car rows)))
+			      (if (is-markup? frow 'tr)
+				  (markup-option-add! frow '&tl #t))))
+		       (if (eq? rules 'header)
+			   (let ((frow (car rows)))
+			      (if (is-markup? frow 'tr)
+				  (markup-option-add! frow '&bl #t))))
+		       (when (and (pair? (cdr rows))
+				  (memq rules '(rows all)))
+			  (for-each (lambda (row)
+				       (if (is-markup? row 'tr)
+					   (markup-option-add! row '&bl #t)))
+				    rows)
+			  (markup-option-add! (car (last-pair rows)) '&bl #f))
+		       (if (and (memq rules '(rows all))
+				(or (not (eq? cstyle 'collapse))
+				    (not (memq frame '(border box above hsides)))))
+			   (let ((lrow (car (last-pair rows))))
+			      (if (is-markup? lrow 'tr)
+				  (markup-option-add! lrow '&bl #t))))))))
+   :after (lambda (n e)
+	     (case (markup-option n :frame)
+		((hsides below box border)
+		 (output (new markup
+			    (markup '&latex-table-hline)
+			    (parent n)
+			    (ident (format #f "~a-below" (markup-ident n)))
+			    (class "table-hline-below"))
+			 e)))
+	     (output (new markup
+			(markup '&latex-table-stop)
+			(class "table")
+			(options `((width ,(markup-option n :width)))))
+		     e)))
+
+;*---------------------------------------------------------------------*/
+;*    &latex-table-hline                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-table-hline
+   :action "\\hline\n")
+
+;*---------------------------------------------------------------------*/
+;*    tr ...                                                           */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tr
+   :options '()
+   :action (lambda (n e)
+	      (let* ((parent (ast-parent n))
+		     (_ (if (not (is-markup? parent 'table))
+			    (skribe-type-error 'tr "Illegal parent, " parent
+					       "#<table>")))
+		     (nbcols (markup-option parent '&nbcols))
+		     (lhs (markup-option parent '&lhs))
+		     (rhs (markup-option parent '&rhs))
+		     (rules (markup-option parent :rules))
+		     (collapse (eq? (markup-option parent :cellstyle) 
+				    'collapse))
+		     (vrules (memq rules '(cols all)))
+		     (cells (markup-body n)))
+		 (if (markup-option n '&tl)
+		     (output (new markup
+				(markup '&latex-table-hline)
+				(parent n)
+				(ident (markup-ident n))
+				(class (markup-class n)))
+			     e))
+		 (if (> nbcols 0)
+		     (let laap ((nbc nbcols)
+				(cs cells))
+			(if (null? cs)
+			    (when (> nbc 1)
+			       (display " & ")
+			       (laap (- nbc 1) cs))
+			    (let* ((c (car cs))
+				   (nc (- nbc (markup-option c :colspan))))
+			       (when (= nbcols nbc)
+				  (cond
+				     ((and lhs vrules (not collapse))
+				      (markup-option-add! c '&lhs "||"))
+				     ((or lhs vrules)
+				      (markup-option-add! c '&lhs #\|))))
+			       (when (= nc 0)
+				  (cond
+				     ((and rhs vrules (not collapse))
+				      (markup-option-add! c '&rhs "||"))
+				     ((or rhs vrules)
+				      (markup-option-add! c '&rhs #\|))))
+			       (when (and vrules (> nc 0) (< nc nbcols))
+				  (markup-option-add! c '&rhs #\|))
+			       (output c e)
+			       (when (> nc 0)
+				  (display " & ")
+				  (laap nc (cdr cs)))))))))
+   :after (lambda (n e)
+	     (display "\\\\")
+	     (if (markup-option n '&bl)
+		 (output (new markup
+			    (markup '&latex-table-hline)
+			    (parent n)
+			    (ident (markup-ident n))
+			    (class (markup-class n)))
+			 e)
+		 (newline))))
+
+;*---------------------------------------------------------------------*/
+;*    tc                                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tc
+   :options '(:width :align :valign :colspan)
+   :action (lambda (n e)
+	      (let ((id (markup-ident n))
+		    (cla (markup-class n)))
+		 (let* ((o0 (markup-body n))
+			(o1 (if (eq? (markup-option n 'markup) 'th)
+				(new markup
+				   (markup '&latex-th)
+				   (parent n)
+				   (ident id)
+				   (class cla)
+				   (options (markup-options n))
+				   (body o0))
+				o0))
+			(o2 (if (markup-option n :width)
+				(new markup
+				   (markup '&latex-tc-parbox)
+				   (parent n)
+				   (ident id)
+				   (class cla)
+				   (options (markup-options n))
+				   (body o1))
+				o1))
+			(o3 (if (or (> (markup-option n :colspan) 1)
+				    (not (eq? (markup-option n :align) 
+					      'center))
+				    (markup-option n '&lhs)
+				    (markup-option n '&rhs))
+				(new markup
+				   (markup '&latex-tc-multicolumn)
+				   (parent n)
+				   (ident id)
+				   (class cla)
+				   (options (markup-options n))
+				   (body o2))
+				o2)))
+		    (output o3 e)))))
+
+;*---------------------------------------------------------------------*/
+;*    &latex-th ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-th
+   :before "\\textsf{"
+   :after "}")
+
+;*---------------------------------------------------------------------*/
+;*    &latex-tc-parbox ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-tc-parbox
+   :before (lambda (n e)
+	      (let ((width (markup-option n :width))
+		    (valign (markup-option n :valign)))
+		 (printf "\\parbox{~a}{" (latex-width width))))
+   :after "}")
+		 
+;*---------------------------------------------------------------------*/
+;*    &latex-tc-multicolumn ...                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer '&latex-tc-multicolumn
+   :before (lambda (n e)
+	      (let ((colspan (markup-option n :colspan))
+		    (lhs (or (markup-option n '&lhs) ""))
+		    (rhs (or (markup-option n '&rhs) ""))
+		    (align (case (markup-option n :align)
+			      ((left) #\l)
+			      ((center) #\c)
+			      ((right) #\r)
+			      (else #\c))))
+		 (printf "\\multicolumn{~a}{~a~a~a}{" colspan lhs align rhs)))
+   :after "}")
+
+;*---------------------------------------------------------------------*/
+;*    image ... @label image@                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'image
+   :options '(:file :url :width :height :zoom)
+   :action (lambda (n e)
+	      (let* ((file (markup-option n :file))
+		     (url (markup-option n :url))
+		     (width (markup-option n :width))
+		     (height (markup-option n :height))
+		     (zoom (markup-option n :zoom))
+		     (body (markup-body n))
+		     (efmt (engine-custom e 'image-format))
+		     (img (or url (convert-image file 
+						 (if (list? efmt) 
+						     efmt
+						     '("eps"))))))
+		 (if (not (string? img))
+		     (skribe-error 'latex "Illegal image" file)
+		     (begin
+			(printf "\\epsfig{file=~a" (strip-ref-base img))
+			(if width (printf ", width=~a" (latex-width width)))
+			(if height (printf ", height=~apt" height))
+			(if zoom (printf ", zoom=\"~a\"" zoom))
+			(display "}"))))))
+
+;*---------------------------------------------------------------------*/
+;*    Ornaments ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'roman :before "{\\textrm{" :after "}}")
+(markup-writer 'bold :before "{\\textbf{" :after "}}")
+(markup-writer 'underline :before  "{\\underline{" :after "}}")
+(markup-writer 'emph :before "{\\em{" :after "}}")
+(markup-writer 'it :before "{\\textit{" :after "}}")
+(markup-writer 'code :before "{\\texttt{" :after "}}")
+(markup-writer 'var :before "{\\texttt{" :after "}}")
+(markup-writer 'sc :before "{\\sc{" :after "}}")
+(markup-writer 'sf :before "{\\sf{" :after "}}")
+(markup-writer 'sub :before "\\begin{math}\\sb{\\mbox{" :after "}}\\end{math}")
+(markup-writer 'sup :before "\\begin{math}\\sp{\\mbox{" :after "}}\\end{math}")
+
+(markup-writer 'tt
+   :before "{\\texttt{"
+   :action (lambda (n e)
+	      (let ((ne (make-engine
+			   (gensym 'latex)
+			   :delegate e
+			   :filter (make-string-replace latex-tt-encoding)
+			   :custom (engine-customs e)
+			   :symbol-table (engine-symbol-table e))))
+		 (output (markup-body n) ne)))
+   :after "}}")
+
+;*---------------------------------------------------------------------*/
+;*    q ... @label q@                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'q
+   :before "``"
+   :after "''")
+
+;*---------------------------------------------------------------------*/
+;*    mailto ... @label mailto@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mailto
+   :options '(:text)
+   :before "{\\texttt{"
+   :action (lambda (n e)
+	      (let ((text (markup-option n :text)))
+		 (output (or text (markup-body n)) e)))
+   :after "}}")
+
+;*---------------------------------------------------------------------*/
+;*    mark ... @label mark@                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mark
+   :before (lambda (n e)
+	      (printf "\\label{~a}" (string-canonicalize (markup-ident n)))))
+
+;*---------------------------------------------------------------------*/
+;*    ref ... @label ref@                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer 'ref
+   :options '(:text :chapter :section :subsection :subsubsection :figure :mark :handle :page)
+   :action (lambda (n e)
+	      (let ((t (markup-option n :text)))
+		 (if t
+		     (begin
+			(output t e)
+			(output "~" e (markup-writer-get '~ e))))))
+   :after (lambda (n e)
+	     (let* ((c (handle-ast (markup-body n)))
+		    (id (markup-ident c)))
+		(if (markup-option n :page)
+		    (printf "\\begin{math}{\\pageref{~a}}\\end{math}" 
+			    (string-canonicalize id))
+		    (printf "\\ref{~a}" 
+			    (string-canonicalize id))))))
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e)
+	      (output (markup-option (handle-ast (markup-body n)) :title) e))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref+ ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref+
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e) 
+	      (let loop ((rs (markup-body n)))
+		 (cond
+		    ((null? rs)
+		     #f)
+		    (else
+		     (if (is-markup? (car rs) 'bib-ref)
+			 (invoke (writer-action (markup-writer-get 'bib-ref e))
+				 (car rs)
+				 e)
+			 (output (car rs) e))
+		     (if (pair? (cdr rs))
+			 (begin
+			    (display ",")
+			    (loop (cdr rs))))))))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    url-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'url-ref
+   :options '(:url :text)
+   :action (lambda (n e) 
+	      (let ((text (markup-option n :text))
+		    (url (markup-option n :url)))
+		 (if (not text)
+		     (output url e)
+		     (output text e)))))
+
+;*---------------------------------------------------------------------*/
+;*    url-ref hyperref ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer 'url-ref
+   :options '(:url :text)
+   :predicate (lambda (n e)
+		 (engine-custom e 'hyperref))
+   :action (lambda (n e) 
+	      (let ((body (markup-option n :text))
+		    (url (markup-option n :url)))
+		 (if (and body (not (equal? body url)))
+		     (begin
+			(display "\\href{")
+			(display url)
+			(display "}{")
+			(output body e)
+			(display "}"))
+		     (begin
+			(display "\\href{")
+			(display url)
+			(printf "}{~a}" url))))))
+
+;*---------------------------------------------------------------------*/
+;*    line-ref ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'line-ref
+   :options '(:offset)
+   :before "{\\textit{"
+   :action (lambda (n e)
+	      (let ((o (markup-option n :offset))
+		    (v (string->number (markup-option n :text))))
+		 (cond
+		    ((and (number? o) (number? v))
+		     (display (+ o v)))
+		    (else
+		     (display v)))))
+   :after "}}")
+
+;*---------------------------------------------------------------------*/
+;*    &the-bibliography ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-bibliography
+   :before (lambda (n e)
+	      (display "{%
+\\sloppy
+\\sfcode`\\.=1000\\relax
+\\newdimen\\bibindent
+\\bibindent=0em
+\\begin{list}{}{%
+        \\settowidth\\labelwidth{[21]}%
+        \\leftmargin\\labelwidth
+        \\advance\\leftmargin\\labelsep
+        \\advance\\leftmargin\\bibindent
+        \\itemindent -\\bibindent
+        \\listparindent \\itemindent
+        \\itemsep 0pt
+    }%\n"))
+   :after (lambda (n e)
+	     (display "\n\\end{list}}\n")))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry
+   :options '(:title)
+   :action (lambda (n e)
+	      (output n e (markup-writer-get '&bib-entry-label e))
+	      (output n e (markup-writer-get '&bib-entry-body e)))
+   :after "\n")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-title ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-title
+   :predicate (lambda (n e)
+		 (engine-custom e 'hyperref))
+   :action (lambda (n e)
+	      (let* ((t (bold (markup-body n)))
+		     (en (handle-ast (ast-parent n)))
+		     (url (markup-option en 'url))
+		     (ht (if url (ref :url (markup-body url) :text t) t)))
+		 (skribe-eval ht e))))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-label ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-label
+   :options '(:title)
+   :before "\\item[{\\char91}"
+   :action (lambda (n e) (output (markup-option n :title) e))
+   :after "{\\char93}] ")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-url ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-url
+   :action (lambda (n e)
+	      (let* ((en (handle-ast (ast-parent n)))
+		     (url (markup-option en 'url))
+		     (t (bold (markup-body url))))
+		 (skribe-eval (ref :url (markup-body url) :text t) e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-comment ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (it (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+	      
+;*---------------------------------------------------------------------*/
+;*    &source-line-comment ...                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-line-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+	      
+;*---------------------------------------------------------------------*/
+;*    &source-keyword ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-keyword
+   :action (lambda (n e)
+	      (skribe-eval (underline (markup-body n)) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &source-error ...                                                */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-error
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-error-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'error-color) cc)
+			     (color :fg cc (underline n1))
+			     (underline n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-define ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-define
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-define-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-module ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-module
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-module-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-markup ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-markup
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-markup-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-thread ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-thread
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-thread-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-string ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-string
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-string-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-bracket ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-bracket
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-bracket-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-type ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-key ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-key
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-type ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg "red" (bold n1))
+			     (bold n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    Restore the base engine                                          */
+;*---------------------------------------------------------------------*/
+(default-engine-set! (find-engine 'base))
diff --git a/src/guile/skribilo/engine/lout.scm b/src/guile/skribilo/engine/lout.scm
new file mode 100644
index 0000000..893ab2e
--- /dev/null
+++ b/src/guile/skribilo/engine/lout.scm
@@ -0,0 +1,2891 @@
+;;; lout.scm  --  A Lout engine.
+;;;
+;;; Copyright 2004, 2005, 2006  Ludovic Courtès <ludovic.courtes@laas.fr>
+;;;
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;; USA.
+
+;;;    Taken from `lcourtes@laas.fr--2004-libre',
+;;;               `skribe-lout--main--0.2--patch-15'.
+;;;    Based on `latex.skr', copyright 2003, 2004 Manuel Serrano.
+
+
+(define-skribe-module (skribilo engine lout)
+  :autoload (ice-9 popen)   (open-output-pipe)
+  :autoload (ice-9 rdelim)  (read-line))
+
+
+
+;*---------------------------------------------------------------------*/
+;*    lout-verbatim-encoding ...                                       */
+;*---------------------------------------------------------------------*/
+(define-public lout-verbatim-encoding
+   '((#\/ "\"/\"")
+     (#\\ "\"\\\\\"")
+     (#\| "\"|\"")
+     (#\& "\"&\"")
+     (#\@ "\"@\"")
+     (#\" "\"\\\"\"")
+     (#\{ "\"{\"")
+     (#\} "\"}\"")
+     (#\$ "\"$\"")
+     (#\# "\"#\"")
+     (#\_ "\"_\"")
+     (#\~ "\"~\"")))
+
+;*---------------------------------------------------------------------*/
+;*    lout-encoding ...                                                */
+;*---------------------------------------------------------------------*/
+(define-public lout-encoding
+  `(,@lout-verbatim-encoding
+    (#\ç "{ @Char ccedilla }")
+    (#\Ç "{ @Char Ccdeilla }")
+    (#\â "{ @Char acircumflex }")
+    (#\Â "{ @Char Acircumflex }")
+    (#\à "{ @Char agrave }")
+    (#\À "{ @Char Agrave }")
+    (#\é "{ @Char eacute }")
+    (#\É "{ @Char Eacute }")
+    (#\è "{ @Char egrave }")
+    (#\È "{ @Char Egrave }")
+    (#\ê "{ @Char ecircumflex }")
+    (#\Ê "{ @Char Ecircumflex }")
+    (#\ù "{ @Char ugrave }")
+    (#\Ù "{ @Char Ugrave }")
+    (#\û "{ @Char ucircumflex }")
+    (#\Û "{ @Char Ucircumflex }")
+    (#\ø "{ @Char oslash }")
+    (#\ô "{ @Char ocircumflex }")
+    (#\Ô "{ @Char Ocircumflex }")
+    (#\ö "{ @Char odieresis }")
+    (#\Ö "{ @Char Odieresis }")
+    (#\î "{ @Char icircumflex }")
+    (#\Î "{ @Char Icircumflex }")
+    (#\ï "{ @Char idieresis }")
+    (#\Ï "{ @Char Idieresis }")
+    (#\] "\"]\"")
+    (#\[ "\"[\"")
+    (#\» "{ @Char guillemotright }")
+    (#\« "{ @Char guillemotleft }")))
+
+
+;; XXX:  This is just here for experimental purposes.
+(define lout-french-punctuation-encoding
+  (let ((space (lambda (before after thing)
+		 (string-append "{ "
+				(if before
+				    (string-append "{ " before " @Wide {} }")
+				    "")
+				"\"" thing "\""
+				(if after
+				    (string-append "{ " after " @Wide {} }")
+				    "")
+				" }"))))
+    `((#\; ,(space "0.5s" #f ";"))
+      (#\? ,(space "0.5s" #f ";"))
+      (#\! ,(space "0.5s" #f ";")))))
+
+(define lout-french-encoding
+  (let ((punctuation (map car lout-french-punctuation-encoding)))
+    (append (let loop ((ch lout-encoding)
+		       (purified '()))
+	      (if (null? ch)
+		  purified
+		  (loop (cdr ch)
+			(if (member (car ch) punctuation)
+			    purified
+			    (cons (car ch) purified)))))
+	    lout-french-punctuation-encoding)))
+
+;*---------------------------------------------------------------------*/
+;*    lout-symbol-table ...                                            */
+;*---------------------------------------------------------------------*/
+(define (lout-symbol-table sym math)
+   `(("iexcl" "{ @Char exclamdown }")
+     ("cent" "{ @Char cent }")
+     ("pound" "{ @Char sterling }")
+     ("yen" "{ @Char yen }")
+     ("section" "{ @Char section }")
+     ("mul" "{ @Char multiply }")
+     ("copyright" "{ @Char copyright }")
+     ("lguillemet" "{ @Char guillemotleft }")
+     ("not" "{ @Char logicalnot }")
+     ("degree" "{ @Char degree }")
+     ("plusminus" "{ @Char plusminus }")
+     ("micro" "{ @Char mu }")
+     ("paragraph" "{ @Char paragraph }")
+     ("middot" "{ @Char periodcentered }")
+     ("rguillemet" "{ @Char guillemotright }")
+     ("1/4" "{ @Char onequarter }")
+     ("1/2" "{ @Char onehalf }")
+     ("3/4" "{ @Char threequarters }")
+     ("iquestion" "{ @Char questiondown }")
+     ("Agrave" "{ @Char Agrave }")
+     ("Aacute" "{ @Char Aacute }")
+     ("Acircumflex" "{ @Char Acircumflex }")
+     ("Atilde" "{ @Char Atilde }")
+     ("Amul" "{ @Char Adieresis }") ;;; FIXME:  Why `mul' and not `uml'?!
+     ("Aring" "{ @Char Aring }")
+     ("AEligature" "{ @Char oe }")
+     ("Oeligature" "{ @Char OE }")  ;;; FIXME:  Should be `OEligature'?!
+     ("Ccedilla" "{ @Char Ccedilla }")
+     ("Egrave" "{ @Char Egrave }")
+     ("Eacute" "{ @Char Eacute }")
+     ("Ecircumflex" "{ @Char Ecircumflex }")
+     ("Euml" "{ @Char Edieresis }")
+     ("Igrave" "{ @Char Igrave }")
+     ("Iacute" "{ @Char Iacute }")
+     ("Icircumflex" "{ @Char Icircumflex }")
+     ("Iuml" "{ @Char Idieresis }")
+     ("ETH" "{ @Char Eth }")
+     ("Ntilde" "{ @Char Ntilde }")
+     ("Ograve" "{ @Char Ograve }")
+     ("Oacute" "{ @Char Oacute }")
+     ("Ocircumflex" "{ @Char Ocircumflex }")
+     ("Otilde" "{ @Char Otilde }")
+     ("Ouml" "{ @Char Odieresis }")
+     ("times" ,(sym "multiply"))
+     ("Oslash" "{ @Char oslash }")
+     ("Ugrave" "{ @Char Ugrave }")
+     ("Uacute" "{ @Char Uacute }")
+     ("Ucircumflex" "{ @Char Ucircumflex }")
+     ("Uuml" "{ @Char Udieresis }")
+     ("Yacute" "{ @Char Yacute }")
+     ("szlig" "{ @Char germandbls }")
+     ("agrave" "{ @Char agrave }")
+     ("aacute" "{ @Char aacute }")
+     ("acircumflex" "{ @Char acircumflex }")
+     ("atilde" "{ @Char atilde }")
+     ("amul" "{ @Char adieresis }")
+     ("aring" "{ @Char aring }")
+     ("aeligature" "{ @Char ae }")
+     ("oeligature" "{ @Char oe }")
+     ("ccedilla" "{ @Char ccedilla }")
+     ("egrave" "{ @Char egrave }")
+     ("eacute" "{ @Char eacute }")
+     ("ecircumflex" "{ @Char ecircumflex }")
+     ("euml" "{ @Char edieresis }")
+     ("igrave" "{ @Char igrave }")
+     ("iacute" "{ @Char iacute }")
+     ("icircumflex" "{ @Char icircumflex }")
+     ("iuml" "{ @Char idieresis }")
+     ("ntilde" "{ @Char ntilde }")
+     ("ograve" "{ @Char ograve }")
+     ("oacute" "{ @Char oacute }")
+     ("ocurcumflex" "{ @Char ocircumflex }") ;; FIXME: `ocIrcumflex'
+     ("otilde" "{ @Char otilde }")
+     ("ouml" "{ @Char odieresis }")
+     ("divide" "{ @Char divide }")
+     ("oslash" "{ @Char oslash }")
+     ("ugrave" "{ @Char ugrave }")
+     ("uacute" "{ @Char uacute }")
+     ("ucircumflex" "{ @Char ucircumflex }")
+     ("uuml" "{ @Char udieresis }")
+     ("yacute" "{ @Char yacute }")
+     ("ymul" "{ @Char ydieresis }")  ;; FIXME: `yUMl'
+     ;; Greek
+     ("Alpha" ,(sym "Alpha"))
+     ("Beta" ,(sym "Beta"))
+     ("Gamma" ,(sym "Gamma"))
+     ("Delta" ,(sym "Delta"))
+     ("Epsilon" ,(sym "Epsilon"))
+     ("Zeta" ,(sym "Zeta"))
+     ("Eta" ,(sym "Eta"))
+     ("Theta" ,(sym "Theta"))
+     ("Iota" ,(sym "Iota"))
+     ("Kappa" ,(sym "Kappa"))
+     ("Lambda" ,(sym "Lambda"))
+     ("Mu" ,(sym "Mu"))
+     ("Nu" ,(sym "Nu"))
+     ("Xi" ,(sym "Xi"))
+     ("Omicron" ,(sym "Omicron"))
+     ("Pi" ,(sym "Pi"))
+     ("Rho" ,(sym "Rho"))
+     ("Sigma" ,(sym "Sigma"))
+     ("Tau" ,(sym "Tau"))
+     ("Upsilon" ,(sym "Upsilon"))
+     ("Phi" ,(sym "Phi"))
+     ("Chi" ,(sym "Chi"))
+     ("Psi" ,(sym "Psi"))
+     ("Omega" ,(sym "Omega"))
+     ("alpha" ,(sym "alpha"))
+     ("beta" ,(sym "beta"))
+     ("gamma" ,(sym "gamma"))
+     ("delta" ,(sym "delta"))
+     ("epsilon" ,(sym "epsilon"))
+     ("zeta" ,(sym "zeta"))
+     ("eta" ,(sym "eta"))
+     ("theta" ,(sym "theta"))
+     ("iota" ,(sym "iota"))
+     ("kappa" ,(sym "kappa"))
+     ("lambda" ,(sym "lambda"))
+     ("mu" ,(sym "mu"))
+     ("nu" ,(sym "nu"))
+     ("xi" ,(sym "xi"))
+     ("omicron" ,(sym "omicron"))
+     ("pi" ,(sym "pi"))
+     ("rho" ,(sym "rho"))
+     ("sigmaf" ,(sym "sigmaf")) ;; FIXME!
+     ("sigma" ,(sym "sigma"))
+     ("tau" ,(sym "tau"))
+     ("upsilon" ,(sym "upsilon"))
+     ("phi" ,(sym "phi"))
+     ("chi" ,(sym "chi"))
+     ("psi" ,(sym "psi"))
+     ("omega" ,(sym "omega"))
+     ("thetasym" ,(sym "thetasym"))
+     ("piv" ,(sym "piv")) ;; FIXME!
+     ;; punctuation
+     ("bullet" ,(sym "bullet"))
+     ("ellipsis" ,(sym "ellipsis"))
+     ("weierp" "{ @Sym  weierstrass }")
+     ("image" ,(sym "Ifraktur"))
+     ("real" ,(sym "Rfraktur"))
+     ("tm" ,(sym "trademarksans")) ;; alt: @Sym trademarkserif
+     ("alef" ,(sym "aleph"))
+     ("<-" ,(sym "arrowleft"))
+     ("<--" "{ { 1.6 1 } @Scale { @Sym arrowleft } }") ;; copied from `eqf'
+     ("uparrow" ,(sym "arrowup"))
+     ("->" ,(sym "arrowright"))
+     ("-->" "{ { 1.6 1 } @Scale { @Sym arrowright } }")
+     ("downarrow" ,(sym "arrowdown"))
+     ("<->" ,(sym "arrowboth"))
+     ("<-->" "{ { 1.6 1 } @Scale { @Sym arrowboth } }")
+     ("<+" ,(sym "carriagereturn"))
+     ("<=" ,(sym "arrowdblleft"))
+     ("<==" "{ { 1.6 1 } @Scale { @Sym arrowdblleft } }")
+     ("Uparrow" ,(sym "arrowdblup"))
+     ("=>" ,(sym "arrowdblright"))
+     ("==>" "{ { 1.6 1 } @Scale { @Sym arrowdblright } }")
+     ("Downarrow" ,(sym "arrowdbldown"))
+     ("<=>" ,(sym "arrowdblboth"))
+     ("<==>" "{ { 1.6 1 } @Scale { @Sym arrowdblboth } }")
+     ;; Mathematical operators (we try to avoid `@Eq' since it
+     ;; requires to `@SysInclude { eq }' -- one solution consists in copying
+     ;; the symbol definition from `eqf')
+     ("forall" "{ { Symbol Base } @Font \"\\042\" }")
+     ("partial" ,(sym "partialdiff"))
+     ("exists" "{ { Symbol Base } @Font \"\\044\" }")
+     ("emptyset" "{ { Symbol Base } @Font \"\\306\" }")
+     ("infinity" ,(sym "infinity"))
+     ("nabla" "{ { Symbol Base } @Font \"\\321\" }")
+     ("in" ,(sym "element"))
+     ("notin" ,(sym "notelement"))
+     ("ni" "{ 180d @Rotate @Sym element }")
+     ("prod" ,(sym "product"))
+     ("sum" ,(sym "summation"))
+     ("asterisk" ,(sym "asteriskmath"))
+     ("sqrt" ,(sym "radical"))
+     ("propto" ,(math "propto"))
+     ("angle" ,(sym "angle"))
+     ("and" ,(math "bwedge"))
+     ("or" ,(math "bvee"))
+     ("cap" ,(math "bcap"))
+     ("cup" ,(math "bcup"))
+     ("integral" ,(math "int"))
+     ("models" ,(math "models"))
+     ("vdash" ,(math "vdash"))
+     ("dashv" ,(math "dashv"))
+     ("sim" ,(sym "similar"))
+     ("cong" ,(sym "congruent"))
+     ("approx" ,(sym "approxequal"))
+     ("neq" ,(sym "notequal"))
+     ("equiv" ,(sym "equivalence"))
+     ("le" ,(sym "lessequal"))
+     ("ge" ,(sym "greaterequal"))
+     ("subset" ,(sym "propersubset"))
+     ("supset" ,(sym "propersuperset"))
+     ("subseteq" ,(sym "reflexsubset"))
+     ("supseteq" ,(sym "reflexsuperset"))
+     ("oplus" ,(sym "circleplus"))
+     ("otimes" ,(sym "circlemultiply"))
+     ("perp" ,(sym "perpendicular"))
+     ("mid" ,(sym "bar"))
+     ("lceil" ,(sym "bracketlefttp"))
+     ("rceil" ,(sym "bracketrighttp"))
+     ("lfloor" ,(sym "bracketleftbt"))
+     ("rfloor" ,(sym "bracketrightbt"))
+     ("langle" ,(sym "angleleft"))
+     ("rangle" ,(sym "angleright"))
+     ;; Misc
+     ("loz" "{ @Lozenge }")
+     ("spades" ,(sym "spade"))
+     ("clubs" ,(sym "club"))
+     ("hearts" ,(sym "heart"))
+     ("diams" ,(sym "diamond"))
+     ("euro" "{ @Euro }")
+     ;; Lout
+     ("dag" "{ @Dagger }")
+     ("ddag" "{ @DaggerDbl }")
+     ("circ" ,(math "circle"))
+     ("top" ,(math "top"))
+     ("bottom" ,(math "bot"))
+     ("lhd" ,(math "triangleleft"))
+     ("rhd" ,(math "triangleright"))
+     ("parallel" ,(math "dbar"))))
+
+
+;;; Debugging support
+
+(define *lout-debug?* #f)
+
+(define-macro (lout-debug fmt . args)
+  `(if *lout-debug?*
+       (with-output-to-port (current-error-port)
+	  (lambda ()
+	     (printf (string-append ,fmt "~%") ,@args
+		     (current-error-port))))
+       #t))
+
+(define-public (lout-tagify ident)
+  ;; Return an "clean" identifier (a string) based on `ident' (a string),
+  ;; suitable for Lout as an `@Tag' value.
+  (let ((tag-encoding '((#\, "-")
+			(#\( "-")
+			(#\) "-")
+			(#\[ "-")
+			(#\] "-")
+			(#\/ "-")
+			(#\| "-")
+			(#\& "-")
+			(#\@ "-")
+			(#\! "-")
+			(#\? "-")
+			(#\: "-")
+			(#\; "-")))
+	(tag (string-canonicalize ident)))
+    ((make-string-replace tag-encoding) tag)))
+
+
+;; Default values of various customs (procedures)
+
+(define (lout-definitions engine)
+  ;; Return a string containing a set of useful Lout definitions that should
+  ;; be inserted at the beginning of the output document.
+  (let ((leader (engine-custom engine 'toc-leader))
+	(leader-space (engine-custom engine 'toc-leader-space)))
+    (apply string-append
+	   `("# @SkribeMark implements Skribe's marks "
+	     "(i.e. cross-references)\n"
+	     "def @SkribeMark\n"
+	     "    right @Tag\n"
+	     "{\n"
+	     "    @PageMark @Tag\n"
+	     "}\n\n"
+
+	     "# @SkribiloLeaders is used in `toc'\n"
+	     "# (this is mostly copied from the expert's guide)\n"
+	     "def @SkribiloLeaders { "
+	     ,leader " |" ,leader-space " @SkribiloLeaders }\n\n"))))
+
+(define (lout-make-doc-cover-sheet doc engine)
+  ;; Create a cover sheet for node `doc' which is a doc-style Lout document.
+  ;; This is the default implementation, i.e. the default value of the
+  ;; `doc-cover-sheet-proc' custom.
+  (let ((title (markup-option doc :title))
+	(author (markup-option doc :author))
+	(date-line (engine-custom engine 'date-line))
+	(cover-sheet? (engine-custom engine 'cover-sheet?))
+	(multi-column? (> (engine-custom engine 'column-number) 1)))
+
+    (if multi-column?
+	;; In single-column document, `@FullWidth' yields a blank page.
+	(display "\n@FullWidth {"))
+    (display "\n//3.0fx\n")
+    (display "\n@Center 1.4f @Font @B { cragged nohyphen 1.4fx } @Break { ")
+    (if title
+       (output title engine)
+       (display "The Lout Document"))
+    (display " }\n")
+    (display "//1.7fx\n")
+    (if date-line
+	(begin
+	  (display "@Center { ")
+	  (output date-line engine)
+	  (display " }\n//1.4fx\n")))
+    (if author
+       (begin
+         (display "@Center { ")
+         (output author engine)
+         (display " }\n")
+         (display "//4fx\n")))
+    (if multi-column?
+	(display "\n} # @FullWidth\n"))))
+
+(define (lout-split-external-link markup)
+  ;; Shorten `markup', an URL `url-ref' markup, by splitting it into an URL
+  ;; `ref' followed by plain text.  This is useful because Lout's
+  ;; @ExternalLink symbols are unbreakable to the embodied text should _not_
+  ;; be too large (otherwise it is scaled down).
+  (let* ((url (markup-option markup :url))
+	 (text (or (markup-option markup :text) url)))
+    (lout-debug "lout-split-external-link: text=~a" text)
+    (cond ((pair? text)
+	   ;; no need to go recursive here: we'll get called again later
+	   `(,(ref :url url :text (car text)) ,@(cdr text)))
+
+	  ((string? text)
+	   (let ((len (string-length text)))
+	     (if (> (- len 8) 2)
+		 ;; don't split on a whitespace or it will vanish
+		 (let ((split (let loop ((where 10))
+				(if (= 0 where)
+				    10
+				    (if (char=? (string-ref text
+							    (- where 1))
+						#\space)
+					(loop (- where 1))
+					where)))))
+		   `(,(ref :url url :text (substring text 0 split))
+		     ,(substring text split len)))
+		 (list markup))))
+
+	  ((markup? text)
+	   (let ((kind (markup-markup text)))
+	     (lout-debug "lout-split-external-link: kind=~a" kind)
+	     (if (member kind '(bold it underline))
+		 ;; get the ornament markup out of the `:text' argument
+		 (list (apply (eval kind (interaction-environment))
+			      (list (ref :url url
+					 :text (markup-body text)))))
+ 		 ;; otherwise, leave it as is
+		 (list markup))))
+
+	  (else (list markup)))))
+
+(define (lout-make-toc-entry node engine)
+  ;; Default implementation of the `toc-entry-proc' custom that produces the
+  ;; number and title of `node' for use in the table of contents.
+  (let ((num (markup-option node :number))
+	(title (markup-option node :title))
+	(lang (engine-custom engine 'initial-language)))
+    (if num
+	(begin
+	  (if (is-markup? node 'chapter) (display "@B { "))
+	  (printf "~a. |2s " (lout-structure-number-string node))
+	  (output title engine)
+	  (if (is-markup? node 'chapter) (display " }")))
+	(if (is-markup? node 'chapter)
+	    (output (bold title) engine)
+	    (output title engine)))))
+
+(define (lout-bib-refs-sort/number entry1 entry2)
+  ;; Default implementation of the `bib-refs-sort-proc' custom.  Compare
+  ;; bibliography entries `entry1' and `entry2' (of type `&bib-entry') for
+  ;; use by `sort' in `bib-ref+'.
+  (let ((ident1 (markup-option entry1 :title))
+	(ident2 (markup-option entry2 :title)))
+    (if (and (markup? ident1) (markup? ident2))
+	(< (markup-option ident1 'number)
+	   (markup-option ident2 'number))
+	(begin
+	  (fprint (current-error-port) "i1: " ident1 ", " entry1)
+	  (fprint (current-error-port) "i2: " ident2 ", " entry2)))))
+
+(define (lout-pdf-bookmark-title node engine)
+  ;; Default implementation of the `pdf-bookmark-title-proc' custom that
+  ;; returns a title (a string) for the PDF bookmark of `node'.
+  (let ((number (lout-structure-number-string node)))
+    (string-append  (if (string=? number "") "" (string-append number ". "))
+		    (ast->string (markup-option node :title)))))
+
+(define (lout-pdf-bookmark-node? node engine)
+  ;; Default implementation of the `pdf-bookmark-node-pred' custom that
+  ;; returns a boolean.
+  (or (is-markup? node 'chapter)
+      (is-markup? node 'section)
+      (is-markup? node 'subsection)
+      (is-markup? node 'slide)))
+
+
+
+
+;*---------------------------------------------------------------------*/
+;*    lout-engine ...                                                 */
+;*---------------------------------------------------------------------*/
+(define lout-engine
+  (make-engine 'lout
+	       :version 0.2
+	       :format "lout"
+	       :delegate (find-engine 'base)
+	       :filter (make-string-replace lout-encoding)
+	       :custom `(;; The underlying Lout document type, i.e. one
+			 ;; of `doc', `report', `book' or `slides'.
+			 (document-type doc)
+
+			 ;; Document style file include line (a string
+			 ;; such as `@Include { doc-style.lout }') or
+			 ;; `auto' (symbol) in which case the include
+			 ;; file is deduced from `document-type'.
+			 (document-include auto)
+
+			 (includes "@SysInclude { tbl }\n")
+			 (initial-font "Palatino Base 10p")
+			 (initial-break
+			  ,(string-append "unbreakablefirst "
+					  "unbreakablelast "
+					  "hyphen adjust 1.2fx"))
+
+			 ;; The document's language, used for hyphenation
+			 ;; and other things.
+			 (initial-language "English")
+
+			 ;; Number of columns.
+			 (column-number 1)
+
+			 ;; First page number.
+			 (first-page-number 1)
+
+			 ;; Page orientation, `portrait', `landscape',
+			 ;; `reverse-portrait' or `reverse-landscape'.
+			 (page-orientation portrait)
+
+			 ;; For reports, whether to produce a cover
+			 ;; sheet.  The `doc-cover-sheet-proc' custom may
+			 ;; also honor this custom for `doc' documents.
+			 (cover-sheet? #t)
+
+			 ;; For reports, the date line.
+			 (date-line #t)
+
+			 ;; For reports, an abstract.
+			 (abstract #f)
+
+			 ;; For reports, title/name of the abstract.  If
+			 ;; `#f', the no abstract title will be
+			 ;; produced.  If `#t', a default name in the
+			 ;; current language is chosen.
+			 (abstract-title #t)
+
+			 ;; Whether to optimize pages.
+			 (optimize-pages? #f)
+
+			 ;; For docs, the procedure that produces the
+			 ;; Lout code for the cover sheet or title.
+			 (doc-cover-sheet-proc
+			  ,lout-make-doc-cover-sheet)
+
+			 ;; Procedure used to sort bibliography
+			 ;; references when several are referred to at
+			 ;; the same time, as in:
+			 ;;  (ref :bib '("smith03" "jones98")) .
+			 ;; By default they are sorted by number.  If
+			 ;; `#f' is given, they are left as is.
+			 (bib-refs-sort-proc
+			  ,lout-bib-refs-sort/number)
+
+			 ;; Lout code for paragraph gaps (similar to
+			 ;; `@PP' with `@ParaGap' equal to `1.0vx' by
+			 ;; default)
+			 (paragraph-gap
+			  "\n//1.0vx @ParaIndent @Wide &{0i}\n")
+
+			 ;; For multi-page tables, it may be
+			 ;; useful to set this to `#t'.  However,
+			 ;; this looks kind of buggy.
+			 (use-header-rows? #f)
+
+			 ;; Tells whether to use Skribe's footnote
+			 ;; numbers or Lout's numbering scheme (the
+			 ;; latter may be better, typography-wise).
+			 (use-skribe-footnote-numbers? #t)
+
+			 ;; A procedure that is passed the engine
+			 ;; and produces Lout definitions.
+			 (inline-definitions-proc ,lout-definitions)
+
+			 ;; A procedure that takes a URL `ref' markup and
+			 ;; returns a list containing (maybe) one such
+			 ;; `ref' markup.  This custom can be used to
+			 ;; modified the way URLs are rendered.  The
+			 ;; default value is a procedure that limits the
+			 ;; size of Lout's @ExternalLink symbols since
+			 ;; they are unbreakable.  In order to completely
+			 ;; disable use of @ExternalLinks, just set it to
+			 ;; `markup-body'.
+			 (transform-url-ref-proc
+			  ,lout-split-external-link)
+
+			 ;; Leader used in the table of contents entries.
+			 (toc-leader ".")
+
+			 ;; Inter-leader spacing in the TOC entries.
+			 (toc-leader-space "2.5s")
+
+			 ;; Procedure that takes a large-scale structure
+			 ;; (chapter, section, etc.) and the engine and
+			 ;; produces the number and possibly title of
+			 ;; this structure for use the TOC.
+			 (toc-entry-proc ,lout-make-toc-entry)
+
+			 ;; The Lout program name, only useful when using
+			 ;; `lout-illustration' on other back-ends.
+			 (lout-program-name "lout")
+
+			 ;; Title and author information in the PDF
+			 ;; document information.  If `#t', the
+			 ;; document's `:title' and `:author' are used.
+			 (pdf-title #t)
+			 (pdf-author #t)
+
+			 ;; Keywords (a list of string) in the PDF
+			 ;; document information.  This custom is deprecated,
+                         ;; use the `:keywords' option of `document' instead.
+			 (pdf-keywords #f)
+
+			 ;; Extra PDF information, an alist of key-value
+			 ;; pairs (string pairs).
+			 (pdf-extra-info (("SkribeVersion"
+					   ,(skribe-release))))
+
+			 ;; Tells whether to produce PDF "docinfo"
+			 ;; (meta-information with title, author,
+			 ;; keywords, etc.).
+			 (make-pdf-docinfo? #t)
+
+			 ;; Tells whether a PDF outline
+			 ;; (aka. "bookmarks") should be produced.
+			 (make-pdf-outline? #t)
+
+			 ;; Procedure that takes a node and an engine and
+			 ;; return a string representing the title of
+			 ;; that node's PDF bookmark.
+			 (pdf-bookmark-title-proc ,lout-pdf-bookmark-title)
+
+			 ;; Procedure that takes a node and an engine and
+			 ;; returns true if that node should have a PDF
+			 ;; outline entry.
+			 (pdf-bookmark-node-pred ,lout-pdf-bookmark-node?)
+
+			 ;; Procedure that takes a node and an engine and
+			 ;; returns true if the bookmark for that node
+			 ;; should be closed ("folded") when the user
+			 ;; opens the PDF document.
+			 (pdf-bookmark-closed-pred
+			  ,(lambda (n e)
+			     (not (is-markup? n 'chapter))))
+
+			 ;; color
+			 (color? #t)
+
+			 ;; source fontification
+			 (source-color #t)
+			 (source-comment-color "#ffa600")
+			 (source-define-color "#6959cf")
+			 (source-module-color "#1919af")
+			 (source-markup-color "#1919af")
+			 (source-thread-color "#ad4386")
+			 (source-string-color "red")
+			 (source-bracket-color "red")
+			 (source-type-color "#00cf00"))
+
+	       :symbol-table (lout-symbol-table
+			      (lambda (m)
+				;; We don't use `@Sym' because it doesn't
+				;; work within `@Eq'.
+				(string-append "{ { Symbol Base } @Font "
+					       "@Char \"" m "\" }"))
+			      (lambda (m)
+				(format #f "{ @Eq { ~a } }" m)))))
+
+
+;; So that calls to `markup-writer' automatically use `lout-engine'...
+(push-default-engine lout-engine)
+
+
+
+;; User-level implementation of PDF bookmarks.
+;;
+;; Basically, Lout code is produced that produces (via `@Graphic') PostScript
+;; code.  That PostScript code is a `pdfmark' command (see Adobe's "pdfmark
+;; Reference Manual") which, when converted to PDF (e.g. with `ps2pdf'),
+;; produces a PDF outline, aka. "bookmarks" (see "PDF Reference, Fifth
+;; Edition", section 8.2.2).
+
+(define (lout-internal-dest-name ident)
+  ;; Return the Lout-generated `pdfmark' named destination for `ident'.  This
+  ;; function mimics Lout's `ConvertToPDFName ()', in `z49.c' (Lout's
+  ;; PostScript back-end).  In Lout, `ConvertToPDFName ()' produces
+  ;; destination names for the `/Dest' function of the `pdfmark' operator.
+  ;; This implementation is valid as of Lout 3.31 and hopefully it won't
+  ;; change in the future.
+  (string-append "LOUT"
+		 (list->string (map (lambda (c)
+				      (if (or (char-alphabetic? c)
+					      (char-numeric? c))
+					  c
+					  #\_))
+				    (string->list ident)))))
+
+(define (lout-pdf-bookmark node children closed? engine)
+  ;; Return the PostScript `pdfmark' operation (a string) that creates a PDF
+  ;; bookmark for node `node'.  `children' is the number of children of
+  ;; `node' in the PDF outline.  If `closed?' is true, then the bookmark will
+  ;; be close (i.e. its children are hidden).
+  ;;
+  ;; Note:  Here, we use a `GoTo' action, while we could instead simply
+  ;; produce a `/Page' attribute without having to use the
+  ;; `lout-internal-dest-name' hack.  The point for doing this is that Lout's
+  ;; `@PageOf' operator doesn't return an "actual" page number within the
+  ;; document, but rather a "typographically correct" page number (e.g. `i'
+  ;; for the cover sheet, `1' for the second page, etc.).  See
+  ;; http://lists.planix.com/pipermail/lout-users/2005q1/003925.html for
+  ;; details.
+  (let* ((filter-title (make-string-replace `(,@lout-verbatim-encoding
+					      (#\newline " "))))
+	 (make-bookmark-title (lambda (n e)
+				(filter-title
+				 ((engine-custom
+				   engine 'pdf-bookmark-title-proc)
+				  n e))))
+	 (ident (markup-ident node)))
+    (string-append "["
+		   (if (= 0 children)
+		       ""
+		       (string-append "\"/\"Count "
+				      (if closed? "-" "")
+				      (number->string children) " "))
+		   "\"/\"Title \"(\"" (make-bookmark-title node engine)
+		   "\")\" "
+		   (if (not ident) ""
+		       (string-append "\"/\"Action \"/\"GoTo \"/\"Dest \"/\""
+				      (lout-internal-dest-name ident) " "))
+		   "\"/\"OUT pdfmark\n")))
+
+(define (lout-pdf-outline node engine . children)
+  ;; Return the PDF outline string (in the form of a PostScript `pdfmark'
+  ;; command) for `node' whose child nodes are assumed to be `children',
+  ;; unless `node' is a document.
+  (let* ((choose-node? (lambda (n)
+			 ((engine-custom engine 'pdf-bookmark-node-pred)
+			  n engine)))
+	 (nodes (if (document? node)
+		    (filter choose-node? (markup-body node))
+		    children)))
+    (apply string-append
+	   (map (lambda (node)
+		  (let* ((children (filter choose-node? (markup-body node)))
+			 (closed? ((engine-custom engine
+						  'pdf-bookmark-closed-pred)
+				   node engine))
+			 (bm (lout-pdf-bookmark node (length children)
+						closed? engine)))
+		    (string-append bm (apply lout-pdf-outline
+					     `(,node ,engine ,@children)))))
+		nodes))))
+
+(define-public (lout-embedded-postscript-code postscript)
+  ;; Return a string embedding PostScript code `postscript' into Lout code.
+  (string-append "\n"
+		 "{ @BackEnd @Case {\n"
+		 "    PostScript @Yield {\n"
+		 postscript
+		 "        }\n"
+		 "} } @Graphic { }\n"))
+
+(define-public (lout-pdf-docinfo doc engine)
+  ;; Produce PostScript code that will produce PDF document information once
+  ;; converted to PDF.
+  (let* ((filter-string (make-string-replace `(,@lout-verbatim-encoding
+					       (#\newline " "))))
+	 (docinfo-field (lambda (key value)
+			  (string-append "\"/\"" key " \"(\""
+					 (filter-string value)
+					 "\")\"\n")))
+	 (author (let ((a (engine-custom engine 'pdf-author)))
+		   (if (or (string? a) (ast? a))
+		       a
+		       (markup-option doc :author))))
+	 (title  (let ((t (engine-custom engine 'pdf-title)))
+		   (if (or (string? t) (ast? t))
+		       t
+		       (markup-option doc :title))))
+	 (keywords (or (engine-custom engine 'pdf-keywords)
+                       (map ast->string
+                            (or (markup-option doc :keywords) '()))))
+	 (extra-fields (engine-custom engine 'pdf-extra-info)))
+
+    (string-append "[ "
+		   (if title
+		       (docinfo-field "Title" (ast->string title))
+		       "")
+		   (if author
+		       (docinfo-field "Author"
+				      (or (cond ((markup? author)
+						 (ast->string
+						  (or (markup-option
+						       author :name)
+						      (markup-option
+						       author :affiliation))))
+						((string? author) author)
+						(else (ast->string author)))
+					  ""))
+		       "")
+		   (if (pair? keywords)
+		       (docinfo-field "Keywords"
+                                      (apply string-append
+                                             (keyword-list->comma-separated
+                                              keywords)))
+		       "")
+		   ;; arbitrary key-value pairs, see sect. 4.7, "Info
+		   ;; dictionary" of the `pdfmark' reference.
+		   (if (or (not extra-fields) (null? extra-fields))
+		       ""
+		       (apply string-append
+			      (map (lambda (p)
+				     (docinfo-field (car p) (cadr p)))
+				   extra-fields)))
+		   "\"/\"DOCINFO pdfmark\n")))
+
+(define-public (lout-output-pdf-meta-info doc engine)
+  ;; Produce PDF bookmarks (aka. "outline") for document `doc', as well as
+  ;; document meta-information (or "docinfo").  This function makes sure that
+  ;; both are only produced once, and only if the relevant customs ask for
+  ;; them.
+  (if (and doc (engine-custom engine 'make-pdf-outline?)
+	   (not (markup-option doc '&pdf-outline-produced?)))
+      (begin
+	(display
+	 (lout-embedded-postscript-code (lout-pdf-outline doc engine)))
+	(markup-option-add! doc '&pdf-outline-produced? #t)))
+  (if (and doc (engine-custom engine 'make-pdf-docinfo?)
+	   (not (markup-option doc '&pdf-docinfo-produced?)))
+      (begin
+	(display
+	 (lout-embedded-postscript-code (lout-pdf-docinfo doc engine)))
+	(markup-option-add! doc '&pdf-docinfo-produced? #t))))
+
+
+
+;*---------------------------------------------------------------------*/
+;*    lout ...                                                         */
+;*---------------------------------------------------------------------*/
+(define-markup (!lout fmt #!rest opt)
+   (if (engine-format? "lout")
+       (apply ! fmt opt)
+       #f))
+
+;*---------------------------------------------------------------------*/
+;*    lout-width ...                                                   */
+;*---------------------------------------------------------------------*/
+(define (lout-width width)
+   (cond ((inexact? width) ;; a relative size (XXX: was `flonum?')
+	  ;; FIXME: Hack ahead: assuming A4 with a 2.5cm margin
+	  ;; on both sides
+	  (let* ((orientation (let ((lout (find-engine 'lout)))
+				 (or (and lout
+					  (engine-custom lout
+							 'page-orientation))
+				     'portrait)))
+		 (margins 5)
+		 (paper-width (case orientation
+				 ((portrait reverse-portrait)
+				  (- 21 margins))
+				 (else (- 29.7 margins)))))
+	     (string-append (number->string (* paper-width
+					       (/ (abs width) 100.)))
+			    "c")))
+	 ((string? width) ;; an engine-dependent width
+	  width)
+	 (else ;; an absolute "pixel" size
+	  (string-append (number->string width) "p"))))
+
+;*---------------------------------------------------------------------*/
+;*    lout-font-size ...                                               */
+;*---------------------------------------------------------------------*/
+(define (lout-font-size size)
+   (case size
+      ((4) "3.5f")
+      ((3) "2.0f")
+      ((2) "1.5f")
+      ((1) "1.2f")
+      ((0) "1.0f")
+      ((-1) "0.8f")
+      ((-2) "0.5f")
+      ((-3) "0.3f")
+      ((-4) "0.2f")
+      (else (if (number? size)
+		(if (< size 0) "0.3f" "1.5f")
+		"1.0f"))))
+
+(define-public (lout-color-specification skribe-color)
+  ;; Return a Lout color name, ie. a string which is either an English color
+  ;; name or something like "rgb 0.5 0.2 0.6".  `skribe-color' is a string
+  ;; representing a Skribe color such as "black" or "#ffffff".
+   (let ((b&w? (let ((lout (find-engine 'lout)))
+		  (and lout (not (engine-custom lout 'color?)))))
+	 (actual-color
+	  (if (and (string? skribe-color)
+		   (char=? (string-ref skribe-color 0) #\#))
+	      (string->number (substring skribe-color 1
+					 (string-length skribe-color))
+			      16)
+	      skribe-color)))
+      (receive (r g b)
+	 (skribe-color->rgb actual-color)
+	 (apply format #f
+		(cons "rgb ~a ~a ~a"
+		      (map (if b&w?
+			       (let ((avg (exact->inexact (/ (+ r g b)
+							     (* 256 3)))))
+				  (lambda (x) avg))
+			       (lambda (x)
+				 (exact->inexact (/ x 256))))
+			   (list r g b)))))))
+
+;*---------------------------------------------------------------------*/
+;*    ~ ...                                                           */
+;*---------------------------------------------------------------------*/
+(markup-writer '~ :before "~" :action #f)
+
+(define (lout-page-orientation orientation)
+  ;; Return a string representing the Lout page orientation name for symbol
+  ;; `orientation'.
+  (let* ((alist '((portrait . "Portrait")
+		  (landscape . "Landscape")
+		  (reverse-portrait . "ReversePortrait")
+		  (reverse-landscape . "ReverseLandscape")))
+	 (which (assoc orientation alist)))
+    (if (not which)
+	(skribe-error 'lout
+		      "`page-orientation' should be either `portrait' or `landscape'"
+		      orientation)
+	(cdr which))))
+
+
+;*---------------------------------------------------------------------*/
+;*    document ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'document
+   :options '(:title :author :ending :keywords :env)
+   :before (lambda (n e) ;; `e' is the engine
+	     (let* ((doc-type (let ((d (engine-custom e 'document-type)))
+				(if (string? d)
+				    (begin
+				      (engine-custom-set! e 'document-type
+							  (string->symbol d))
+				      (string->symbol d))
+				    d)))
+		    (doc-style? (eq? doc-type 'doc))
+		    (slides? (eq? doc-type 'slides))
+		    (doc-include (engine-custom e 'document-include))
+		    (includes (engine-custom e 'includes))
+		    (font (engine-custom e 'initial-font))
+		    (lang (engine-custom e 'initial-language))
+		    (break (engine-custom e 'initial-break))
+		    (column-number (engine-custom e 'column-number))
+		    (first-page-number (engine-custom e 'first-page-number))
+		    (page-orientation (engine-custom e 'page-orientation))
+		    (title (markup-option n :title)))
+
+	       ;; Add this markup option, used by
+	       ;; `lout-start-large-scale-structure' et al.
+	       (markup-option-add! n '&substructs-started? #f)
+
+	       (if (eq? doc-include 'auto)
+		   (case doc-type
+		     ((report)  (display "@SysInclude { report }\n"))
+		     ((book)    (display "@SysInclude { book }\n"))
+		     ((doc)     (display "@SysInclude { doc }\n"))
+		     ((slides)  (display "@SysInclude { slides }\n"))
+		     (else     (skribe-error
+				'lout
+				"`document-type' should be one of `book', `report', `doc' or `slides'"
+				doc-type)))
+		   (printf "# Custom document includes\n~a\n" doc-include))
+
+	       (if includes
+		   (printf "# Additional user includes\n~a\n" includes)
+		   (display "@SysInclude { tbl }\n"))
+
+	       ;; Write additional Lout definitions
+	       (display (lout-definitions e))
+
+	       (case doc-type
+		 ((report) (display "@Report\n"))
+		 ((book)   (display "@Book\n"))
+		 ((doc)    (display "@Document\n"))
+		 ((slides) (display "@OverheadTransparencies\n")))
+
+	       (display (string-append "  @InitialSpace { tex } "
+				       "# avoid having too many spaces\n"))
+
+	       ;; The `doc' style doesn't have @Title, @Author and the likes
+	       (if (not doc-style?)
+		   (begin
+		     (display "  @Title { ")
+		     (if title
+			 (output title e)
+			 (display "The Lout-Skribe Book"))
+		     (display " }\n")
+
+		     ;; The author
+		     (let* ((author (markup-option n :author)))
+
+		       (display "  @Author { ")
+		       (output author e)
+		       (display " }\n")
+
+		       ;; Lout reports support `@Institution' while books
+		       ;; don't.
+		       (if (and (eq? doc-type 'report)
+				(is-markup? author 'author))
+			   (let ((institution (markup-option author
+							     :affiliation)))
+			     (if institution
+				 (begin
+				   (printf "  @Institution { ")
+				   (output institution e)
+				   (printf " }\n"))))))))
+
+	       ;; Lout reports make it possible to choose whether to prepend
+	       ;; a cover sheet (books and docs don't).  Same for a date
+	       ;; line.
+	       (if (eq? doc-type 'report)
+		   (let ((cover-sheet?   (engine-custom e 'cover-sheet?))
+			 (date-line      (engine-custom e 'date-line))
+			 (abstract       (engine-custom e 'abstract))
+			 (abstract-title (engine-custom e 'abstract-title)))
+		     (display (string-append "  @CoverSheet { "
+					     (if cover-sheet?
+						 "Yes" "No")
+					     " }\n"))
+		     (display "  @DateLine { ")
+		     (if (string? date-line)
+			 (output date-line e)
+			 (display (if date-line "Yes" "No")))
+		     (display " }\n")
+
+		     (if abstract
+			 (begin
+			   (if (not (eq? abstract-title #t))
+			       (begin
+				 (display "  @AbstractTitle { ")
+				 (cond
+				  ((not abstract-title) #t)
+				  (else (output abstract-title e)))
+				 (display " }\n")))
+
+			   (display "  @Abstract {\n")
+			   (output abstract e)
+			   (display "\n}\n")))))
+
+	       (printf "  @OptimizePages { ~a }\n"
+		       (if (engine-custom e 'optimize-pages?)
+			   "Yes" "No"))
+
+	       (printf "  @InitialFont { ~a }\n"
+		       (cond ((string? font) font)
+			     ((symbol? font)
+			      (string-append (symbol->string font)
+					     " Base 10p"))
+			     ((number? font)
+			      (string-append "Palatino Base "
+					     (number->string font)
+					     "p"))
+			     (#t
+			      (skribe-error
+			       'lout 'initial-font
+			       "Should be a Lout font name, a symbol, or a number"))))
+	       (printf "  @InitialBreak { ~a }\n"
+		       (if break break "adjust 1.2fx hyphen"))
+	       (if (not slides?)
+		   (printf "  @ColumnNumber { ~a }\n"
+			   (if (number? column-number)
+			       column-number 1)))
+	       (printf "  @FirstPageNumber { ~a }\n"
+		       (if (number? first-page-number)
+			   first-page-number 1))
+	       (printf "  @PageOrientation { ~a }\n"
+		       (lout-page-orientation page-orientation))
+	       (printf "  @InitialLanguage { ~a }\n"
+		       (if lang lang "English"))
+
+	       ;; FIXME: Insert a preface for text preceding the first ch.
+	       ;; FIXME: Create an @Introduction for the first chapter
+	       ;;        if its title is "Introduction" (for books).
+
+	       (display "//\n\n")
+
+	       (if doc-style?
+		   ;; `doc' documents don't have @Title and the likes so
+		   ;; we need to implement them "by hand"
+		   (let ((make-cover-sheet
+			  (engine-custom e 'doc-cover-sheet-proc)))
+		     (display "@Text @Begin\n")
+		     (if make-cover-sheet
+			 (make-cover-sheet n e)
+			 (lout-make-doc-cover-sheet n e))))
+
+	       (if doc-style?
+		   ;; Putting it here will only work with `doc' documents.
+		   (lout-output-pdf-meta-info n e))))
+
+   :after (lambda (n e)
+	    (let ((doc-type (engine-custom e 'document-type)))
+	      (if (eq? doc-type 'doc)
+		  (begin
+		    (if (markup-option n '&substructs-started?)
+			(display "\n@EndSections\n"))
+		    (display "\n@End @Text\n")))
+	      (display "\n\n# Lout document ends here.\n"))))
+
+
+;*---------------------------------------------------------------------*/
+;*    author ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'author
+   :options '(:name :title :affiliation :email :url :address
+	      :phone :photo :align)
+
+   :action (lambda (n e)
+	      (let ((doc-type (engine-custom e 'document-type))
+		    (name (markup-option n :name))
+		    (title (markup-option n :title))
+		    (affiliation (markup-option n :affiliation))
+		    (email (markup-option n :email))
+		    (url (markup-option n :url))
+		    (address (markup-option n :address))
+		    (phone (markup-option n :phone))
+		    (photo (markup-option n :photo)))
+
+		(define (row x)
+		  (display "\n//1.5fx\n@Center { ")
+		  (output x e)
+		  (display " }\n"))
+
+		(if email
+		    (row (list (if name name "")
+			       (! " <@I{")
+			       (cond ((string? email) email)
+				     ((markup? email)
+				      (markup-body email))
+				     (#t ""))
+			       (! "}> ")))
+		    (if name (row name)))
+
+		(if title (row title))
+
+		;; In reports, the affiliation is passed to `@Institution'.
+		;; However, books do not have an `@Institution' parameter.
+		(if (and affiliation (not (eq? doc-type 'report)))
+		    (row affiliation))
+
+		(if address (row address))
+		(if phone (row phone))
+		(if url (row (it url)))
+		(if photo (row photo)))))
+
+
+(define (lout-toc-entry node depth engine)
+  ;; Produce a TOC entry of depth `depth' (a integer greater than or equal to
+  ;; zero) for `node' using engine `engine'.  The Lout code here is mostly
+  ;; copied from Lout's `dsf' (see definition of `@Item').
+  (let ((ident (markup-ident node))
+	(entry-proc (engine-custom engine 'toc-entry-proc)))
+    (if (markup-option node :toc)
+	(begin
+	  (display "@LP\n")
+	  (if ident
+	      ;; create an internal for PDF navigation
+	      (printf "{ ~a } @LinkSource { " (lout-tagify ident)))
+
+	  (if (> depth 0)
+	      (printf "|~as " (number->string (* 6 depth))))
+	  (display " @HExpand { ")
+
+	  ;; output the number and title of this node
+	  (entry-proc node engine)
+
+	  (display " &1rt @OneCol { ")
+	  (printf " @SkribiloLeaders & @PageOf { ~a }"
+		  (lout-tagify (markup-ident node)))
+	  (display " &0io } }")
+
+	  (if ident (display " }"))
+	  (display "\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    toc ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'toc
+   :options '(:class :chapter :section :subsection)
+   :action (lambda (n e)
+	     (display "\n# toc\n")
+	     (if (markup-option n :chapter)
+		 (let ((chapters (filter (lambda (n)
+					    (or (is-markup? n 'chapter)
+						(is-markup? n 'slide)))
+					 (markup-body (ast-document n)))))
+		   (for-each (lambda (c)
+			       (let ((sections
+				      (search-down (lambda (n)
+						     (is-markup? n 'section))
+						   c)))
+				 (lout-toc-entry c 0 e)
+				 (if (markup-option n :section)
+				     (for-each
+				      (lambda (s)
+					(lout-toc-entry s 1 e)
+					(if (markup-option n :subsection)
+					    (let ((subs
+						   (search-down
+						    (lambda (n)
+						      (is-markup?
+						       n 'subsection))
+						    s)))
+					      (for-each
+					       (lambda (s)
+						 (lout-toc-entry s 2 e))
+					       subs))))
+				      sections))))
+			     chapters)))))
+
+(define lout-book-markup-alist
+  '((chapter . "Chapter")
+    (section . "Section")
+    (subsection . "SubSection")
+    (subsubsection . "SubSubSection")))
+
+(define lout-report-markup-alist
+  '((chapter . "Section")
+    (section . "SubSection")
+    (subsection . "SubSubSection")
+    (subsubsection . #f)))
+
+(define lout-slides-markup-alist
+  '((slide . "Overhead")))
+
+(define lout-doc-markup-alist lout-report-markup-alist)
+
+(define (lout-structure-markup skribe-markup engine)
+  ;; Return the Lout structure name for `skribe-markup' (eg. "Chapter" for
+  ;; `chapter' markups when `engine''s document type is `book').
+  (let ((doc-type (engine-custom engine 'document-type))
+	(assoc-ref (lambda (alist key)
+		      (and-let* ((as (assoc key alist))) (cdr as)))))
+    (case doc-type
+      ((book)    (assoc-ref lout-book-markup-alist skribe-markup))
+      ((report)  (assoc-ref lout-report-markup-alist skribe-markup))
+      ((doc)     (assoc-ref lout-doc-markup-alist skribe-markup))
+      ((slides)  (assoc-ref lout-slides-markup-alist skribe-markup))
+      (else
+       (skribe-error 'lout
+		     "`document-type' should be one of `book', `report', `doc' or `slides'"
+		     doc-type)))))
+
+(define-public (lout-structure-number-string markup)
+  ;; Return a structure number string such as "1.2".
+  ;; FIXME: External code has started to rely on this.  This should be
+  ;;        generalized and moved elsewhere.
+  (let loop ((struct markup))
+    (if (document? struct)
+	""
+	(let ((parent-num (loop (ast-parent struct)))
+	      (num (markup-option struct :number)))
+	  (string-append parent-num
+			 (if (string=? "" parent-num) "" ".")
+			 (if (number? num) (number->string num) ""))))))
+
+;*---------------------------------------------------------------------*/
+;*    lout-block-before ...                                            */
+;*---------------------------------------------------------------------*/
+(define (lout-block-before n e)
+  ;; Produce the Lout code that introduces node `n', a large-scale
+  ;; structure (chapter, section, etc.).
+  (let ((lout-markup (lout-structure-markup (markup-markup n) e))
+	(title (markup-option n :title))
+	(number (markup-option n :number))
+	(ident (markup-ident n)))
+
+    (if (not lout-markup)
+	(begin
+	   ;; the fallback method (i.e. when there exists no equivalent
+	   ;; Lout markup)
+	   (display "\n//1.8vx\n@B { ")
+	   (output title e)
+	   (display " }\n@SkribeMark { ")
+	   (display (lout-tagify ident))
+	   (display " }\n//0.8vx\n\n"))
+	(begin
+	   (printf "\n@~a\n  @Title { " lout-markup)
+	   (output title e)
+	   (printf " }\n")
+
+	   (if (number? number)
+	       (printf "  @BypassNumber { ~a }\n"
+		       (lout-structure-number-string n))
+	       (if (not number)
+		   ;; this trick hides the section number
+		   (printf "  @BypassNumber { } # unnumbered\n")))
+
+	   (cond ((string? ident)
+		  (begin
+		     (display "  @Tag { ")
+		     (display (lout-tagify ident))
+		     (display " }\n")))
+		 ((symbol? ident)
+		  (begin
+		     (display "  @Tag { ")
+		     (display (lout-tagify (symbol->string ident)))
+		     (display " }\n")))
+		 (#t
+		  (skribe-error 'lout
+				"Node identifiers should be strings"
+				ident)))
+
+	   (display "\n@Begin\n")))))
+
+(define (lout-block-after n e)
+  ;; Produce the Lout code that terminates node `n', a large-scale
+  ;; structure (chapter, section, etc.).
+  (let ((lout-markup (lout-structure-markup (markup-markup n) e)))
+     (if (not lout-markup)
+	 (printf "\n\n//0.3vx\n\n") ;; fallback method
+	 (printf "\n\n@End @~a\n\n" lout-markup))))
+
+
+(define (lout-markup-child-type skribe-markup)
+  ;; Return the child markup type of `skribe-markup' (e.g. for `chapter',
+  ;; return `section').
+  (let loop ((structs '(document chapter section subsection subsubsection)))
+    (if (null? structs)
+	#f
+	(if (eq? (car structs) skribe-markup)
+	    (cadr structs)
+	    (loop (cdr structs))))))
+
+(define (lout-start-large-scale-structure markup engine)
+  ;; Perform the necessary step and produce output as a result of starting
+  ;; large-scale structure `markup' (ie. a chapter, section, subsection,
+  ;; etc.).
+  (let* ((doc-type (engine-custom engine 'document-type))
+	 (doc-style? (eq? doc-type 'doc))
+	 (parent (ast-parent markup))
+	 (markup-type (markup-markup markup))
+	 (lout-markup-name (lout-structure-markup markup-type
+						  engine)))
+    (lout-debug "start-struct: markup=~a parent=~a"
+		markup parent)
+
+    ;; add an `&substructs-started?' option to the markup
+    (markup-option-add! markup '&substructs-started? #f)
+
+    (if (and lout-markup-name
+	     parent (or doc-style? (not (document? parent))))
+	(begin
+	  (if (not (markup-option parent '&substructs-started?))
+	      ;; produce an `@BeginSubSections' or equivalent; `doc'-style
+	      ;; documents need to preprend an `@BeginSections' before the
+	      ;; first section while other styles don't.
+	      (printf "\n@Begin~as\n" lout-markup-name))
+
+	  ;; FIXME: We need to make sure that PARENT is a large-scale
+	  ;; structure, otherwise it won't have the `&substructs-started?'
+	  ;; option (e.g., if PARENT is a `color' markup).  I need to clarify
+	  ;; this.
+	  (if (memq (markup-markup parent)
+		    '(document chapter section subsection subsubsection))
+	      ;; update the `&substructs-started?' option of the parent
+	      (markup-option-set! parent '&substructs-started? #t))
+
+	  (lout-debug "start-struct: updated parent: ~a"
+		      (markup-option parent '&substructs-started?))))
+
+    ;; output the `@Section @Title { ... } @Begin' thing
+    (lout-block-before markup engine)))
+
+(define (lout-end-large-scale-structure markup engine)
+  ;; Produce Lout code for ending structure `markup' (a chapter, section,
+  ;; subsection, etc.).
+  (let* ((doc-type (engine-custom engine 'document-type))
+	 (doc-style? (eq? doc-type 'doc))
+	 (markup-type (markup-markup markup))
+	 (lout-markup-name (lout-structure-markup markup-type
+						  engine)))
+
+    (if (and lout-markup-name
+	     (markup-option markup '&substructs-started?)
+	     (or doc-style? (not (document? markup))))
+	(begin
+	  ;; produce an `@EndSubSections' or equivalent; `doc'-style
+	  ;; documents need to issue an `@EndSections' after the last section
+	  ;; while other types of documents don't.
+	  (lout-debug "end-struct: closing substructs for ~a" markup)
+	  (printf "\n@End~as\n"
+		  (lout-structure-markup (lout-markup-child-type markup-type)
+					 engine))
+	  (markup-option-set! markup '&substructs-started? #f)))
+
+    (lout-block-after markup engine)))
+
+
+;*---------------------------------------------------------------------*/
+;*    section ... .. @label chapter@                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer 'chapter
+   :options '(:title :number :toc :file :env)
+   :validate (lambda (n e)
+	       (document? (ast-parent n)))
+
+   :before (lambda (n e)
+	     (lout-start-large-scale-structure n e)
+
+	     ;; `doc' documents produce their PDF outline right after
+	     ;; `@Text @Begin'; other types of documents must produce it
+	     ;; as part of their first chapter.
+	     (lout-output-pdf-meta-info (ast-document n) e))
+
+   :after lout-end-large-scale-structure)
+
+;*---------------------------------------------------------------------*/
+;*    section ... . @label section@                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'section
+   :options '(:title :number :toc :file :env)
+   :validate (lambda (n e)
+	       (is-markup? (ast-parent n) 'chapter))
+   :before lout-start-large-scale-structure
+   :after lout-end-large-scale-structure)
+
+;*---------------------------------------------------------------------*/
+;*    subsection ... @label subsection@                                */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsection
+   :options '(:title :number :toc :file :env)
+   :validate (lambda (n e)
+	       (is-markup? (ast-parent n) 'section))
+   :before lout-start-large-scale-structure
+   :after lout-end-large-scale-structure)
+
+;*---------------------------------------------------------------------*/
+;*    subsubsection ... @label subsubsection@                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'subsubsection
+   :options '(:title :number :toc :file :env)
+   :validate (lambda (n e)
+	       (is-markup? (ast-parent n) 'subsection))
+   :before lout-start-large-scale-structure
+   :after lout-end-large-scale-structure)
+
+
+;*---------------------------------------------------------------------*/
+;*    paragraph ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'paragraph
+   :options '()
+   :validate (lambda (n e)
+	       (or (eq? 'doc (engine-custom e 'document-type))
+		   (memq (and (markup? (ast-parent n))
+			      (markup-markup (ast-parent n)))
+			 '(chapter section subsection subsubsection slide))))
+   :before (lambda (n e)
+	     (let ((gap (engine-custom e 'paragraph-gap)))
+	       (display (if (string? gap) gap "\n@PP\n")))))
+
+;*---------------------------------------------------------------------*/
+;*    footnote ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'footnote
+   :options '(:label)
+   :before (lambda (n e)
+	     (let ((label (markup-option n :label))
+		   (use-number?
+		    (engine-custom e 'use-skribe-footnote-numbers?)))
+	       (if (or (and (number? label) use-number?) label)
+		   (printf "{ @FootNote @Label { ~a } { "
+			   (if label label ""))
+		   (printf "{ @FootNote ~a{ "
+			   (if (not number) "@Label { } " "")))))
+   :after (lambda (n e)
+	    (display " } }")))
+
+;*---------------------------------------------------------------------*/
+;*    linebreak ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'linebreak
+   :action (lambda (n e)
+	      (display "\n@LP\n")))
+
+;*---------------------------------------------------------------------*/
+;*    hrule ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'hrule
+   :options '()
+   :action "\n@LP\n@FullWidthRule\n@LP\n")
+
+;*---------------------------------------------------------------------*/
+;*    color ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'color
+   :options '(:fg :bg :width)
+   ;; FIXME: `:bg' not supported
+   ;; FIXME: `:width' is not supported either.  Rather use `frame' for that
+   ;; kind of options.
+   :before (lambda (n e)
+	     (let* ((w (markup-option n :width))
+		    (fg (markup-option n :fg)))
+	       (printf "{ ~a } @Color { " (lout-color-specification fg))))
+
+   :after (lambda (n e)
+	    (display " }")))
+
+;*---------------------------------------------------------------------*/
+;*    frame ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'frame
+   ;; @Box won't span over several pages so this may cause
+   ;; problems if large frames are used.  The workaround here consists
+   ;; in using an @Tbl with one single cell.
+   :options '(:width :border :margin :bg)
+   :before (lambda (n e)
+	     (let ((width (markup-option n :width))
+		   (margin (markup-option n :margin))
+		   (border (markup-option n :border))
+		   (bg (markup-option n :bg)))
+
+	       ;; The user manual seems to expect `frame' to imply a
+	       ;; linebreak.  However, the LaTeX engine doesn't seem to
+	       ;; agree.
+	       ;(display "\n@LP")
+	       (printf (string-append "\n@Tbl # frame\n"
+				      "  rule { yes }\n"))
+	       (if border (printf     "  rulewidth { ~a }\n"
+				      (lout-width border)))
+	       (if width  (printf     "  width { ~a }\n"
+				      (lout-width width)))
+	       (if margin (printf     "  margin { ~a }\n"
+				      (lout-width margin)))
+	       (if bg     (printf     "  paint { ~a }\n"
+				      (lout-color-specification bg)))
+	       (display "{ @Row format { @Cell A } A { "))
+
+; 	     (printf "\n@Box linewidth { ~a } margin { ~a } { "
+; 		     (lout-width (markup-option n :width))
+; 		     (lout-width (markup-option n :margin)))
+	     )
+   :after (lambda (n e)
+	    (display " } }\n")))
+
+;*---------------------------------------------------------------------*/
+;*    font ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'font
+   :options '(:size :face)
+   :before (lambda (n e)
+	     (let ((face (markup-option n :face))
+		   (size (lout-font-size (markup-option n :size))))
+	       (printf "\n~a @Font { " size)))
+   :after (lambda (n e)
+	    (display " }\n")))
+
+;*---------------------------------------------------------------------*/
+;*    flush ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'flush
+   :options '(:side)
+   :before (lambda (n e)
+	      (display "\n@LP")
+	      (case (markup-option n :side)
+		 ((center)
+		  (display "\n@Center { # flush-center\n"))
+		 ((left)
+		  (display "\n# flush-left\n"))
+		 ((right)
+		  (display (string-append "\n@Right "
+					  "{ rragged hyphen } @Break "
+					  "{ # flush-right\n")))))
+   :after (lambda (n e)
+	     (case (markup-option n :side)
+		((left)
+		 (display ""))
+		(else
+		 (display "\n}")))
+	     (display " # flush\n")))
+
+;*---------------------------------------------------------------------*/
+;*    center ...                                                       */
+;*---------------------------------------------------------------------*/
+(markup-writer 'center
+   ;; Note: We prepend and append a newline in order to make sure
+   ;; things work as expected.
+   :before "\n@LP\n@Center {"
+   :after "}\n@LP\n")
+
+;*---------------------------------------------------------------------*/
+;*    pre ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'pre
+   :before "\n@LP lines @Break lout @Space { # pre\n"
+   :after "\n} # pre\n")
+
+;*---------------------------------------------------------------------*/
+;*    prog ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'prog
+   :options '(:line :mark)
+   :before "\nlines @Break lout @Space {\n"
+   :after "\n} # @Break\n")
+
+;*---------------------------------------------------------------------*/
+;*    &prog-line ...                                                   */
+;*---------------------------------------------------------------------*/
+;; Program lines appear within a `lines @Break' block.
+(markup-writer '&prog-line
+   :before (lambda (n e)
+	      (let ((n (markup-ident n)))
+		 (if n (skribe-eval (it (list n) ": ") e))))
+   :after "\n")
+
+;*---------------------------------------------------------------------*/
+;*    itemize ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'itemize
+   :options '(:symbol)
+   :before (lambda (n e)
+	     (let ((symbol (markup-option n :symbol)))
+	       (if symbol
+		   (begin
+		     (display "\n@List style { ")
+		     (output symbol e)
+		     (display " } # itemize\n"))
+		   (display "\n@BulletList # itemize\n"))))
+   :after "\n@EndList\n")
+
+;*---------------------------------------------------------------------*/
+;*    enumerate ...                                                    */
+;*---------------------------------------------------------------------*/
+(markup-writer 'enumerate
+   :options '(:symbol)
+   :before (lambda (n e)
+	     (let ((symbol (markup-option n :symbol)))
+	       (if symbol
+		   (printf "\n@List style { ~a } # enumerate\n"
+			   symbol)
+		   (display "\n@NumberedList # enumerate\n"))))
+   :after "\n@EndList\n")
+
+;*---------------------------------------------------------------------*/
+;*    description ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'description
+   :options '(:symbol) ;; `symbol' doesn't make sense here
+   :before "\n@TaggedList # description\n"
+   :action (lambda (n e)
+	      (for-each (lambda (item)
+			   (let ((k (markup-option item :key)))
+			     (display "@DropTagItem { ")
+			     (for-each (lambda (i)
+					 (output i e)
+					 (display " "))
+				       (if (pair? k) k (list k)))
+			     (display " } { ")
+			     (output (markup-body item) e)
+			     (display " }\n")))
+			(markup-body n)))
+   :after "\n@EndList\n")
+
+;*---------------------------------------------------------------------*/
+;*    item ...                                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer 'item
+   :options '(:key)
+   :before "\n@LI { "
+   :after  " }")
+
+;*---------------------------------------------------------------------*/
+;*    blockquote ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer 'blockquote
+   :before "\n@ID {"
+   :after  "\n} # @ID\n")
+
+;*---------------------------------------------------------------------*/
+;*    figure ... @label figure@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'figure
+   :options '(:legend :number :multicolumns)
+   :action (lambda (n e)
+	      (let ((ident (markup-ident n))
+		    (number (markup-option n :number))
+		    (legend (markup-option n :legend))
+		    (mc? (markup-option n :multicolumns)))
+		 (display "\n@Figure\n")
+		 (display "  @Tag { ")
+		 (display (lout-tagify ident))
+		 (display " }\n")
+		 (printf  "  @BypassNumber { ~a }\n"
+			  (cond ((number? number) number)
+				((not number)     "")
+				(else             number)))
+		 (display "  @InitialLanguage { ")
+		 (display (engine-custom e 'initial-language))
+		 (display " }\n")
+
+		 (if legend
+		     (begin
+		       (lout-debug "figure: ~a, \"~a\"" ident legend)
+		       (printf  "  @Caption { ")
+		       (output legend e)
+		       (printf  " }\n")))
+		 (printf "  @Location { ~a }\n"
+			 (if mc? "PageTop" "ColTop"))
+		 (printf  "{\n")
+		 (output (markup-body n) e)))
+   :after (lambda (n e)
+	    (display "}\n")))
+
+
+;*---------------------------------------------------------------------*/
+;*    lout-table-column-number ...                                          */
+;*    -------------------------------------------------------------    */
+;*    This function computes how columns are contained by the table.   */
+;*---------------------------------------------------------------------*/
+(define (lout-table-column-number t)
+   (define (row-columns row)
+      (let loop ((cells (markup-body row))
+		 (nbcols 0))
+	 (if (null? cells)
+	     nbcols
+	     (loop (cdr cells)
+		   (+ nbcols (markup-option (car cells) :colspan))))))
+   (let loop ((rows (markup-body t))
+	      (nbcols 0))
+      (if (null? rows)
+	  nbcols
+	  (loop (cdr rows)
+		(max (row-columns (car rows)) nbcols)))))
+
+(define (lout-table-cell-indent align)
+  ;; Return the Lout name (a string) for cell alignment `align' (a symbol).
+  (case align
+    ((center #f #t) "ctr")
+    ((right)        "right")
+    ((left)         "left")
+    (else (skribe-error 'td align
+			"Unknown alignment type"))))
+
+(define (lout-table-cell-vindent align)
+  ;; Return the Lout name (a string) for cell alignment `align' (a symbol).
+  (case align
+    ((center #f #t) "ctr")
+    ((top)          "top")
+    ((bottom)       "foot")
+    (else (skribe-error 'td align
+			"Unknown alignment type"))))
+
+(define (lout-table-cell-vspan cell-letter row-vspan)
+   ;; Return the vspan information (an alist) for the cell whose
+   ;; letter is `cell-letter', within the row whose vspan information
+   ;; is given by `row-vspan'.  If the given cell doesn't span over
+   ;; rows, then #f is returned.
+   (and-let* ((as (assoc cell-letter row-vspan)))
+	     (cdr as)))
+
+(define (lout-table-cell-vspan-start? vspan-alist)
+   ;; For the cell whose vspan information is given by `vspan-alist',
+   ;; return #t if that cell starts spanning vertically.
+   (and vspan-alist
+	(cdr (assoc 'start? vspan-alist))))
+
+(define-macro (char+int c i)
+  `(integer->char (+ ,i (char->integer ,c))))
+
+(define-macro (-- i)
+  `(- ,i 1))
+
+
+(define (lout-table-cell-option-string cell)
+  ;; Return the Lout cell option string for `cell'.
+  (let ((align (markup-option cell :align))
+	(valign (markup-option cell :valign))
+	(width (markup-option cell :width))
+	(bg (markup-option cell :bg)))
+    (string-append (lout-table-cell-rules cell) " "
+		   (string-append
+		    "indent { "
+		    (lout-table-cell-indent align)
+		    " } ")
+		   (string-append
+		    "indentvertical { "
+		    (lout-table-cell-vindent valign)
+		    " } ")
+		   (if (not width) ""
+		       (string-append "width { "
+				      (lout-width width)
+				      " } "))
+		   (if (not bg) ""
+		       (string-append "paint { "
+				      (lout-color-specification bg)
+				      " } ")))))
+
+(define (lout-table-cell-format-string cell vspan-alist)
+  ;; Return a Lout cell format string for `cell'.  It uses the `&cell-name'
+  ;; markup option of its cell as its Lout cell name and `vspan-alist' as the
+  ;; source of information regarding its vertical spanning (#f means that
+  ;; `cell' is not vertically spanned).
+  (let ((cell-letter (markup-option cell '&cell-name))
+	(cell-options (lout-table-cell-option-string cell))
+	(colspan (if vspan-alist
+		     (cdr (assoc 'hspan vspan-alist))
+		     (markup-option cell :colspan)))
+	(vspan-start? (and vspan-alist
+			   (cdr (assoc 'start? vspan-alist)))))
+    (if (and (not vspan-start?) vspan-alist)
+	"@VSpan"
+	(let* ((cell-fmt (string-append "@Cell " cell-options
+					(string cell-letter))))
+	  (string-append
+	   (if (> colspan 1)
+	       (string-append (if (and vspan-start? vspan-alist)
+				  "@StartHVSpan " "@StartHSpan ")
+			      cell-fmt
+			      (let pool ((cnt (- colspan 1))
+					 (span-cells ""))
+				(if (= cnt 0)
+				    span-cells
+				    (pool (- cnt 1)
+					  (string-append span-cells
+							 " | @HSpan")))))
+	       (string-append (if (and vspan-alist vspan-start?)
+				  "@StartVSpan " "")
+			      cell-fmt)))))))
+
+
+(define (lout-table-row-format-string row)
+  ;; Return a Lout row format string for row `row'.  It uses the `&cell-name'
+  ;; markup option of its cell as its Lout cell name.
+
+  ;; FIXME: This function has become quite ugly
+  (let ((cells (markup-body row))
+	(row-vspan (markup-option row '&vspan-alist)))
+
+    (let loop ((cells cells)
+	       (cell-letter #\A)
+	       (delim "")
+	       (fmt ""))
+      (lout-debug "looping on cell ~a" cell-letter)
+
+      (if (null? cells)
+
+	  ;; The final `|' prevents the rightmost column to be
+	  ;; expanded to full page width (see sect. 6.11, p. 133).
+	  (if row-vspan
+	      ;; In the end, there can be vspan columns left so we need to
+	      ;; mark them
+	      (let final-loop ((cell-letter cell-letter)
+			       (fmt fmt))
+		(let* ((cell-vspan (lout-table-cell-vspan cell-letter
+							  row-vspan))
+		       (hspan (if cell-vspan
+				  (cdr (assoc 'hspan cell-vspan))
+				  1)))
+		  (lout-debug "final-loop: ~a ~a" cell-letter cell-vspan)
+		  (if (not cell-vspan)
+		      (string-append fmt " |")
+		      (final-loop (integer->char
+				   (+ hspan (char->integer cell-letter)))
+				  (string-append fmt " | @VSpan |")))))
+
+	      (string-append fmt " |"))
+
+	  (let* ((cell (car cells))
+		 (vspan-alist (lout-table-cell-vspan cell-letter row-vspan))
+		 (vspan-start? (lout-table-cell-vspan-start? vspan-alist))
+		 (colspan (if vspan-alist
+			      (cdr (assoc 'hspan vspan-alist))
+			      (markup-option cell :colspan)))
+		 (cell-format
+		  (lout-table-cell-format-string cell vspan-alist)))
+
+	    (loop (if (or (not vspan-alist) vspan-start?)
+		      (cdr cells)
+		      cells)  ;; don't skip pure vspan cells
+
+		  ;; next cell name
+		  (char+int cell-letter colspan)
+
+		  " | "  ;; the cell delimiter
+		  (string-append fmt delim cell-format)))))))
+
+
+
+;; A row vspan alist describes the cells of a row that span vertically
+;; and it looks like this:
+;;
+;;    ((#\A . ((start? . #t) (hspan . 1) (vspan . 3)))
+;;     (#\C . ((start? . #f) (hspan . 2) (vspan . 1))))
+;;
+;; which means that cell `A' start spanning vertically over three rows
+;; including this one, while cell `C' is an "empty" cell that continues
+;; the vertical spanning of a cell appearing on some previous row.
+;;
+;; The running "global" (or "table-wide") vspan alist looks the same
+;; except that it doesn't have the `start?' tags.
+
+(define (lout-table-compute-row-vspan-alist row global-vspan-alist)
+  ;; Compute the vspan alist of row `row' based on the current table vspan
+  ;; alist `global-vspan-alist'.  As a side effect, this function stores the
+  ;; Lout cell name (a character between #\A and #\Z) as the value of markup
+  ;; option `&cell-name' of each cell.
+  (if (pair? (markup-body row))
+      ;; Mark the first cell as such.
+      (markup-option-add! (car (markup-body row)) '&first-cell? #t))
+
+  (let cell-loop ((cells (markup-body row))
+		  (cell-letter #\A)
+		  (row-vspan-alist '()))
+    (lout-debug "cell: ~a ~a" cell-letter
+		(if (null? cells) '() (car cells)))
+
+    (if (null? cells)
+
+	;; In the end, we must retain any vspan cell that occurs after the
+	;; current cell name (note: we must add a `start?' tag at this point
+	;; since the global table vspan alist doesn't have that).
+	(let ((additional-cells (filter (lambda (c)
+					  (char>=? (car c) cell-letter))
+					global-vspan-alist)))
+	  (lout-debug "compute-row-vspan-alist returning: ~a + ~a (~a)"
+		      row-vspan-alist additional-cells
+		      (length global-vspan-alist))
+	  (append row-vspan-alist
+		  (map (lambda (c)
+			 `(,(car c) . ,(cons '(start? . #f) (cdr c))))
+		       additional-cells)))
+
+	(let* ((current-cell-vspan (assoc cell-letter global-vspan-alist))
+	       (hspan (if current-cell-vspan
+			  (cdr (assoc 'hspan (cdr current-cell-vspan)))
+			  (markup-option (car cells) :colspan))))
+
+	  (if (null? (cdr cells))
+	      ;; Mark the last cell as such
+	      (markup-option-add! (car cells) '&last-cell? #t))
+
+	  (cell-loop (if current-cell-vspan
+			 cells ;; this cell is vspanned, so don't skip it
+			 (cdr cells))
+
+		     ;; next cell name
+		     (char+int cell-letter (or hspan 1))
+
+		     (begin ;; updating the row vspan alist
+		       (lout-debug "cells: ~a" (length cells))
+		       (lout-debug "current-cell-vspan for ~a: ~a"
+				   cell-letter current-cell-vspan)
+
+		       (if current-cell-vspan
+
+			   ;; this cell is currently vspanned, ie. a previous
+			   ;; row defined a vspan for it and that it is still
+			   ;; spanning on this row
+			   (cons `(,cell-letter
+				   . ((start? . #f)
+				      (hspan  . ,(cdr
+						  (assoc
+						   'hspan
+						   (cdr current-cell-vspan))))))
+				 row-vspan-alist)
+
+			   ;; this cell is not currently vspanned
+			   (let ((vspan (markup-option (car cells) :rowspan)))
+			     (lout-debug "vspan-option for ~a: ~a"
+					 cell-letter vspan)
+
+			     (markup-option-add! (car cells)
+						 '&cell-name cell-letter)
+			     (if (and vspan (> vspan 1))
+				 (cons `(,cell-letter . ((start? . #t)
+							 (hspan . ,hspan)
+							 (vspan . ,vspan)))
+				       row-vspan-alist)
+				 row-vspan-alist)))))))))
+
+(define (lout-table-update-table-vspan-alist table-vspan-alist
+					     row-vspan-alist)
+  ;; Update `table-vspan-alist' based on `row-vspan-alist', the alist
+  ;; representing vspan cells for the last row that has been read."
+  (lout-debug "update-table-vspan: ~a and ~a"
+	      table-vspan-alist row-vspan-alist)
+
+  (let ((new-vspan-cells (filter (lambda (cell)
+				   (cdr (assoc 'start? (cdr cell))))
+				 row-vspan-alist)))
+
+    ;; Append the list of new vspan cells described in `row-vspan-alist'
+    (let loop ((cells (append table-vspan-alist new-vspan-cells))
+	       (result '()))
+      (if (null? cells)
+	  (begin
+	    (lout-debug "update-table-vspan returning: ~a" result)
+	    result)
+	  (let* ((cell (car cells))
+		 (cell-letter (car cell))
+		 (cell-hspan (cdr (assoc 'hspan (cdr cell))))
+		 (cell-vspan (-- (cdr (assoc 'vspan (cdr cell))))))
+	    (loop (cdr cells)
+		  (if (> cell-vspan 0)
+
+		      ;; Keep information about this vspanned cell
+		      (cons `(,cell-letter . ((hspan . ,cell-hspan)
+					      (vspan . ,cell-vspan)))
+			    result)
+
+		      ;; Vspan for this cell has been done so we can remove
+		      ;; it from the running table vspan alist
+		      result)))))))
+
+(define (lout-table-mark-vspan! tab)
+  ;; Traverse the rows of table `tab' and add them an `&vspan-alist' option
+  ;; that describes which of its cells are to be vertically spanned.
+  (let loop ((rows (markup-body tab))
+	     (global-vspan-alist '()))
+    (if (null? rows)
+
+	;; At this point, each row holds its own vspan information alist (the
+	;; `&vspan-alist' option) so we don't care anymore about the running
+	;; table vspan alist
+	#t
+
+	(let* ((row (car rows))
+	       (row-vspan-alist (lout-table-compute-row-vspan-alist
+				 row global-vspan-alist)))
+
+	  ;; Bind the row-specific vspan information to the row object
+	  (markup-option-add! row '&vspan-alist row-vspan-alist)
+
+	  (if (null? (cdr rows))
+	      ;; Mark the last row as such
+	      (markup-option-add! row '&last-row? #t))
+
+	  (loop (cdr rows)
+		(lout-table-update-table-vspan-alist global-vspan-alist
+						     row-vspan-alist))))))
+
+(define (lout-table-first-row? row)
+   (markup-option row '&first-row?))
+
+(define (lout-table-last-row? row)
+   (markup-option row '&last-row?))
+
+(define (lout-table-first-cell? cell)
+   (markup-option cell '&first-cell?))
+
+(define (lout-table-last-cell? cell)
+   (markup-option cell '&last-cell?))
+
+(define (lout-table-row-rules row)
+   ;; Return a string representing the Lout option string for
+   ;; displaying rules of `row'.
+   (let* ((table (ast-parent row))
+	  (frames (markup-option table :frame))
+	  (rules (markup-option table :rules))
+	  (first? (lout-table-first-row? row))
+	  (last? (lout-table-last-row? row)))
+      (string-append (if (and first?
+			      (member frames '(above hsides box border)))
+			 "ruleabove { yes } " "")
+		     (if (and last?
+			      (member frames '(below hsides box border)))
+			 "rulebelow { yes } " "")
+		     ;; rules
+		     (case rules
+			((header)
+			 ;; We consider the first row to be a header row.
+			 (if first? "rulebelow { yes }" ""))
+			((rows all)
+			 ;; We use redundant rules because coloring
+			 ;; might make them disappear otherwise.
+			 (string-append (if first? "" "ruleabove { yes } ")
+					(if last? "" "rulebelow { yes }")))
+			(else "")))))
+
+(define (lout-table-cell-rules cell)
+   ;; Return a string representing the Lout option string for
+   ;; displaying rules of `cell'.
+   (let* ((row (ast-parent cell))
+	  (table (ast-parent row))
+	  (frames (markup-option table :frame))
+	  (rules (markup-option table :rules))
+	  (first? (lout-table-first-cell? cell))
+	  (last? (lout-table-last-cell? cell)))
+      (string-append (if (and first?
+			      (member frames '(vsides lhs box border)))
+			 "ruleleft { yes } " "")
+		     (if (and last?
+			      (member frames '(vsides rhs box border)))
+			 "ruleright { yes } " "")
+		     ;; rules
+		     (case rules
+			((cols all)
+			 ;; We use redundant rules because coloring
+			 ;; might make them disappear otherwise.
+			 (string-append (if last? "" "ruleright { yes } ")
+					(if first? "" "ruleleft { yes }")))
+			(else "")))))
+
+;*---------------------------------------------------------------------*/
+;*    table ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'table
+   :options '(:frame :rules :border :width :cellpadding)
+   ;; XXX: `:cellstyle' `separate' and `:cellspacing' not supported
+   ;; by Lout's @Tbl.
+   :before (lambda (n e)
+	      (let ((width (markup-option n :width))
+		    (border (markup-option n :border))
+		    (cp (markup-option n :cellpadding))
+		    (rows (markup-body n)))
+
+		 (define (cell-width row col)
+		    (let ((cells (markup-body row))
+			  (bg (markup-option row :bg)))
+		       (let loop ((cells cells)
+				  (c 0))
+			  (if (pair? cells)
+			      (let* ((ce (car cells))
+				     (width (markup-option ce :width))
+				     (colspan (markup-option ce :colspan)))
+				 (if (= col c)
+				     (if (number? width) width 0)
+				     (loop (cdr cells) (+ c colspan))))
+			      0))))
+
+		 (define (col-width col)
+		    (let loop ((rows rows)
+			       (width 0))
+		       (if (null? rows)
+			   (if (= width 0)
+			       0
+			       width)
+			   (loop (cdr rows)
+				 (max width (cell-width (car rows) col))))))
+
+		 (if (pair? (markup-body n))
+		     ;; Mark the first row as such
+		     (markup-option-add! (car (markup-body n))
+					 '&first-row? #t))
+
+		 ;; Mark each row with vertical spanning information
+		 (lout-table-mark-vspan! n)
+
+		 (display "\n@Tbl # table\n")
+
+		 (if (number? border)
+		     (printf "  rulewidth { ~a }\n"
+			     (lout-width (markup-option n :border))))
+		 (if (number? cp)
+		     (printf "  margin { ~ap }\n"
+			     (number->string cp)))
+
+		 (display "{\n")))
+
+   :after (lambda (n e)
+	    (let ((header-rows (or (markup-option n '&header-rows) 0)))
+	      ;; Issue an `@EndHeaderRow' symbol for each `@HeaderRow' symbol
+	      ;; previously produced.
+	      (let ((cnt header-rows))
+		(if (> cnt 0)
+		    (display "\n@EndHeaderRow"))))
+
+	    (display "\n} # @Tbl\n")))
+
+;*---------------------------------------------------------------------*/
+;*    'tr ...                                                          */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tr
+   :options '(:bg)
+   :action (lambda (row e)
+	     (let* ((bg (markup-option row :bg))
+		    (bg-color (if (not bg) ""
+				  (string-append
+				   "paint { "
+				   (lout-color-specification bg) " } ")))
+		    (first-row? (markup-option row '&first-row?))
+		    (header-row? (any (lambda (n)
+					(eq? (markup-option n 'markup)
+					     'th))
+				      (markup-body row)))
+		    (fmt (lout-table-row-format-string row))
+		    (rules (lout-table-row-rules row)))
+
+	       ;; Use `@FirstRow' and `@HeaderFirstRow' for the first
+	       ;; row.  `@HeaderFirstRow' seems to be buggy though.
+	       ;; (see section 6.1, p.119 of the User's Guide).
+
+	       (printf "\n@~aRow ~aformat { ~a }"
+		       (if first-row? "First" "")
+		       bg-color fmt)
+	       (display (string-append " " rules))
+	       (output (markup-body row) e)
+
+	       (if (and header-row? (engine-custom e 'use-header-rows?))
+		   ;; `@HeaderRow' symbols are not actually printed
+		   ;; (see section 6.11, p. 134 of the User's Guide)
+		   ;; FIXME:  This all seems buggy on the Lout side.
+		   (let* ((tab (ast-parent row))
+			  (hrows (and (markup? tab)
+				      (or (markup-option tab '&header-rows)
+					  0))))
+		     (if (not (is-markup? tab 'table))
+			 (skribe-error 'lout
+				       "tr's parent not a table!" tab))
+		     (markup-option-add! tab '&header-rows (+ hrows 1))
+		     (printf "\n@Header~aRow ~aformat { ~a }"
+			     ""   ; (if first-row? "First" "")
+			     bg-color fmt)
+		     (display (string-append " " rules))
+		     
+		     ;; the cells must be produced once here
+		     (output (markup-body row) e))))))
+
+;*---------------------------------------------------------------------*/
+;*    tc                                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer 'tc
+   :options '(markup :width :align :valign :colspan :rowspan :bg)
+   :before (lambda (cell e)
+	     (printf "\n  ~a { " (markup-option cell '&cell-name)))
+   :after (lambda (cell e)
+	    (display " }")))
+
+
+;*---------------------------------------------------------------------*/
+;*    image ...                                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'image
+   :options '(:file :url :width :height :zoom)
+   :action (lambda (n e)
+	      (let* ((file (markup-option n :file))
+		     (url (markup-option n :url))
+		     (width (markup-option n :width))
+		     (height (markup-option n :height))
+		     (zoom (markup-option n :zoom))
+		     (body (markup-body n))
+		     (efmt (engine-custom e 'image-format))
+		     (img (or url (convert-image file
+						 (if (list? efmt)
+						     efmt
+						     '("eps"))))))
+		(if url ;; maybe we should run `wget' then?  :-)
+		    (skribe-error 'lout "Image URLs not supported" url))
+		(if (not (string? img))
+		    (skribe-error 'lout "Illegal image" file)
+		    (begin
+		      (if width
+			  (printf "\n~a @Wide" (lout-width width)))
+		      (if height
+			  (printf "\n~a @High" (lout-width height)))
+		      (if zoom
+			  (printf "\n~a @Scale" zoom))
+		      (printf "\n@IncludeGraphic { \"~a\" }\n" img))))))
+
+;*---------------------------------------------------------------------*/
+;*    Ornaments ...                                                    */
+;*---------------------------------------------------------------------*/
+;; Each ornament is enclosed in braces to allow such things as
+;; "he,(bold "ll")o" to work without adding an extra space.
+(markup-writer 'roman :before "{ @R { " :after " } }")
+(markup-writer 'underline :before  "{ @Underline { " :after " } }")
+(markup-writer 'code :before "{ @F { " :after " } }")
+(markup-writer 'var :before "{ @F { " :after " } }")
+(markup-writer 'sc :before "{ @S {" :after " } }")
+(markup-writer 'sf :before "{ { Helvetica Base } @Font { " :after " } }")
+(markup-writer 'sub :before "{ @Sub { " :after " } }")
+(markup-writer 'sup :before "{ @Sup { " :after " } }")
+(markup-writer 'tt :before "{ @F { " :after " } }")
+
+
+;; `(bold (it ...))' and `(it (bold ...))' should both lead to `@BI { ... }'
+;; instead of `@B { @I { ... } }' (which is different).
+;; Unfortunately, it is not possible to use `ast-parent' and
+;; `find1-up' to check whether `it' (resp. `bold') was invoked within
+;; a `bold' (resp. `it') markup, hence the `&italics' and `&bold'
+;; option trick.   FIXME:  This would be much more efficient if
+;; `ast-parent' would work as expected.
+
+;; FIXME: See whether `@II' can be useful.  Use SRFI-39 parameters.
+
+(markup-writer 'it
+   :before (lambda (node engine)
+	      (let ((bold-children (search-down (lambda (n)
+						   (is-markup? n 'bold))
+						node)))
+		 (map (lambda (b)
+			 (markup-option-add! b '&italics #t))
+		      bold-children)
+		 (printf "{ ~a { "
+		      (if (markup-option node '&bold)
+			  "@BI" "@I"))))
+   :after " } }")
+
+(markup-writer 'emph
+   :before (lambda (n e)
+	      (invoke (writer-before (markup-writer-get 'it e))
+		      n e))
+   :after (lambda (n e)
+	     (invoke (writer-after (markup-writer-get 'it e))
+		     n e)))
+
+(markup-writer 'bold
+   :before (lambda (node engine)
+	      (let ((it-children (search-down (lambda (n)
+						 (or (is-markup? n 'it)
+						     (is-markup? n 'emph)))
+					      node)))
+		 (map (lambda (i)
+			 (markup-option-add! i '&bold #t))
+		      it-children)
+		 (printf "{ ~a { "
+			 (if (markup-option node '&italics)
+			     "@BI" "@B"))))
+   :after " } }")
+
+;*---------------------------------------------------------------------*/
+;*    q ... @label q@                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer 'q
+   :before "{ @Char guillemotleft }\" \""
+   :after "\" \"{ @Char guillemotright }")
+
+;*---------------------------------------------------------------------*/
+;*    mailto ... @label mailto@                                        */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mailto
+   :options '(:text)
+   :before " @I { "
+   :action (lambda (n e)
+	      (let ((text (markup-option n :text)))
+		 (output (or text (markup-body n)) e)))
+   :after " }")
+
+;*---------------------------------------------------------------------*/
+;*    mark ... @label mark@                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer 'mark
+   :action (lambda (n e)
+	     (if (markup-ident n)
+		 (begin
+		   (display "{ @SkribeMark { ")
+		   (display (lout-tagify (markup-ident n)))
+		   (display " } }"))
+		 (skribe-error 'lout "mark: Node has no identifier" n))))
+
+(define (lout-page-of ident)
+  ;; Return a string for the `@PageOf' statement for `ident'.
+  (let ((tag (lout-tagify ident)))
+    (string-append ", { " tag " } @CrossLink { "
+		   "p. @PageOf { " tag " } }")))
+
+
+;*---------------------------------------------------------------------*/
+;*    ref ... @label ref@                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer 'ref
+   :options '(:text :chapter :section :subsection :subsubsection
+	      :figure :mark :handle :ident :page)
+   :action (lambda (n e)
+	     (let ((url (markup-option n :url))
+		   (text (markup-option n :text))
+		   (mark (markup-option n :mark))
+		   (handle (markup-option n :handle))
+		   (chapter (markup-option n :chapter))
+		   (section (markup-option n :section))
+		   (subsection (markup-option n :subsection))
+		   (subsubsection (markup-option n :subsubsection))
+		   (show-page-num? (markup-option n :page)))
+
+		;; A handle to the target is automagically passed
+		;; as the body of each `ref' instance (see `api.scm').
+		(let* ((target (handle-ast (markup-body n)))
+		       (ident (markup-ident target))
+		       (title (markup-option target :title))
+		       (number (markup-option target :number)))
+		   (lout-debug "ref: target=~a ident=~a" target ident)
+		   (if text (output text e))
+
+		   ;; Marks don't have a number
+		   (if (eq? (markup-markup target) 'mark)
+		       (printf (lout-page-of ident))
+		       (begin
+			  ;; Don't output a section/whatever number
+			  ;; when text is provided in order to be
+			  ;; consistent with the HTML back-end.
+			  ;; Sometimes (eg. for user-defined markups),
+			  ;; we don't even know how to reference them
+			  ;; anyway.
+			  (if (not text)
+			      (printf " @NumberOf { ~a }"
+				      (lout-tagify ident)))
+			  (if show-page-num?
+			      (printf (lout-page-of ident)))))))))
+
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e)
+	     (let ((entry (handle-ast (markup-body n))))
+	       (output (markup-option entry :title) e)))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    bib-ref+ ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'bib-ref+
+   ;; When several references are passed.  Strangely enough, the list of
+   ;; entries passed to this writer (as its body) contains both `bib-ref' and
+   ;; `bib-entry' objects, hence the `canonicalize-entry' function below.
+   :options '(:text :bib)
+   :before "["
+   :action (lambda (n e)
+	     (let* ((entries (markup-body n))
+		    (canonicalize-entry (lambda (x)
+					  (cond
+					   ((is-markup? x 'bib-entry) x)
+					   ((is-markup? x 'bib-ref)
+					    (handle-ast (markup-body x)))
+					   (else
+					    (skribe-error
+					     'lout
+					     "bib-ref+: invalid entry type"
+					     x)))))
+		    (help-proc (lambda (proc)
+				 (lambda (e1 e2)
+				   (proc (canonicalize-entry e1)
+					 (canonicalize-entry e2)))))
+		    (sort-proc (engine-custom e 'bib-refs-sort-proc)))
+	       (let loop ((rs (if sort-proc
+				  (sort entries (help-proc sort-proc))
+				  entries)))
+		 (cond
+		  ((null? rs)
+		   #f)
+		  (else
+		   (if (is-markup? (car rs) 'bib-ref)
+		       (invoke (writer-action (markup-writer-get 'bib-ref e))
+			       (car rs)
+			       e)
+		       (output (car rs) e))
+		   (if (pair? (cdr rs))
+		       (begin
+			 (display ",")
+			 (loop (cdr rs)))))))))
+   :after "]")
+
+;*---------------------------------------------------------------------*/
+;*    url-ref ...                                                      */
+;*---------------------------------------------------------------------*/
+(markup-writer 'url-ref
+   :options '(:url :text)
+   :action (lambda (n e)
+	     (let ((url (markup-option n :url))
+		   (text (markup-option n :text))
+		   (transform (engine-custom e 'transform-url-ref-proc)))
+	       (if (or (not transform)
+		       (markup-option n '&transformed))
+		   (begin
+		     (printf "{ \"~a\" @ExternalLink { " url)
+		     (if text ;; FIXME: Should be (not (string-index text #\space))
+			 (output text e)
+			 (let ((filter-url (make-string-replace
+					    `((#\/ "\"/\"&-")
+					      (#\. ".&-")
+					      (#\- "&-")
+					      (#\_ "_&-")
+					      ,@lout-verbatim-encoding
+					      (#\newline "")))))
+			   ;; Filter the URL in a way to give Lout hints on
+			   ;; where hyphenation should take place.
+			   (fprint (current-error-port) "Here!!!" filter-url)
+			   (display (filter-url url) e)))
+		     (printf " } }"))
+		   (begin
+		     (markup-option-add! n '&transformed #t)
+		     (output (transform n) e))))))
+
+;*---------------------------------------------------------------------*/
+;*    line-ref ...                                                     */
+;*---------------------------------------------------------------------*/
+(markup-writer 'line-ref
+   :options '(:offset)
+   :before "{ @I {" ;; FIXME: Not tested
+   :action (lambda (n e)
+	      (let ((o (markup-option n :offset))
+		    (v (string->number (markup-option n :text))))
+		 (cond
+		    ((and (number? o) (number? v))
+		     (display (+ o v)))
+		    (else
+		     (display v)))))
+   :after "} }")
+
+;*---------------------------------------------------------------------*/
+;*    &the-bibliography ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-bibliography
+   :before (lambda (n e)
+	     ;; Compute the length (in characters) of the longest entry label
+	     ;; so that the label width of the list is adjusted.
+	     (let loop ((entries (markup-body n))
+			(label-width 0))
+	       (if (null? entries)
+		   (begin
+		     (display "\n# the-bibliography\n@LP\n")
+		     ;; usually, the tag with be something like "[7]", hence
+		     ;; the `+ 1' below (`[]' is narrower than 2f)
+		     (printf  "@TaggedList labelwidth { ~af }\n"
+			      (+ 1 label-width)))
+		   (loop (cdr entries)
+			 (let ((entry-length
+				(let liip ((e (car entries)))
+				  (cond
+				   ((markup? e)
+				    (cond ((is-markup? e '&bib-entry)
+					   (liip (markup-option e :title)))
+					  ((is-markup? e '&bib-entry-ident)
+					   (liip (markup-option e 'number)))
+					  (else
+					   (liip (markup-body e)))))
+				   ((string? e)
+				    (string-length e))
+				   ((number? e)
+				    (liip (number->string e)))
+				   ((list? e)
+				    (apply + (map liip e)))
+				   (else 0)))))
+; 			   (fprint (current-error-port)
+; 				   "node=" (car entries)
+; 				   " body=" (markup-body (car entries))
+; 				   " title=" (markup-option (car entries)
+; 							    :title)
+; 				   " len=" entry-length)
+			   (if (> label-width entry-length)
+			       label-width
+			       entry-length))))))
+   :after (lambda (n e)
+	     (display "\n@EndList # the-bibliography (end)\n")))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry ...                                                   */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry
+   :options '(:title)
+   :before "@TagItem "
+   :action (lambda (n e)
+	     (display " { ")
+	     (output n e (markup-writer-get '&bib-entry-label e))
+	     (display " }  { ")
+	     (output n e (markup-writer-get '&bib-entry-body e))
+	     (display " }"))
+   :after "\n")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-title ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-title
+   :action (lambda (n e)
+	      (let* ((t (bold (markup-body n)))
+		     (en (handle-ast (ast-parent n)))
+		     (url (markup-option en 'url))
+		     (ht (if url (ref :url (markup-body url) :text t) t)))
+		 (skribe-eval ht e))))
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-label ...                                             */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-label
+   :options '(:title)
+   :before " \"[\""
+   :action (lambda (n e) (output (markup-option n :title) e))
+   :after "\"]\" ")
+
+;*---------------------------------------------------------------------*/
+;*    &bib-entry-url ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&bib-entry-url
+   :action (lambda (n e)
+	      (let* ((en (handle-ast (ast-parent n)))
+		     (url (markup-option en 'url))
+		     (t (bold (markup-body url))))
+		 (skribe-eval (ref :url (markup-body url) :text t) e))))
+
+;*---------------------------------------------------------------------*/
+;*    &the-index-header ...                                            */
+;*---------------------------------------------------------------------*/
+(markup-writer '&the-index-header
+   :action (lambda (n e)
+	      (display "@Center { ") ;; FIXME:  Needs to be rewritten.
+	      (for-each (lambda (h)
+			   (let ((f (engine-custom e 'index-header-font-size)))
+			      (if f
+				  (skribe-eval (font :size f (bold (it h))) e)
+				  (output h e))
+			      (display " ")))
+			(markup-body n))
+	      (display " }")
+	      (skribe-eval (linebreak 2) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &source-comment ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (it (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-line-comment ...                                         */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-line-comment
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-comment-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-keyword ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-keyword
+   :action (lambda (n e)
+	      (skribe-eval (bold (markup-body n)) e)))
+
+;*---------------------------------------------------------------------*/
+;*    &source-define ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-define
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-define-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-module ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-module
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-module-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-markup ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-markup
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-markup-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-thread ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-thread
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-thread-color))
+		     (n1 (bold (markup-body n)))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-string ...                                               */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-string
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-string-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     n1)))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-bracket ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-bracket
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-bracket-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-type ...                                                 */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-type
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc n1)
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-key ...                                                  */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-key
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg cc (bold n1))
+			     (it n1))))
+		 (skribe-eval n2 e))))
+
+;*---------------------------------------------------------------------*/
+;*    &source-bracket ...                                              */
+;*---------------------------------------------------------------------*/
+(markup-writer '&source-bracket
+   :action (lambda (n e)
+	      (let* ((cc (engine-custom e 'source-type-color))
+		     (n1 (markup-body n))
+		     (n2 (if (and (engine-custom e 'source-color) cc)
+			     (color :fg "red" (bold n1))
+			     (bold n1))))
+		 (skribe-eval n2 e))))
+
+
+;*---------------------------------------------------------------------*/
+;*    Illustrations                                                    */
+;*---------------------------------------------------------------------*/
+(define-public (lout-illustration . args)
+  ;; FIXME: This should be a markup.
+
+  ;; Introduce a Lout illustration (such as a diagram) whose code is either
+  ;; the body of `lout-illustration' or the contents of `file'.  For engines
+  ;; other than Lout, an EPS file is produced and then converted if needed.
+  ;; The `:alt' option is equivalent to HTML's `alt' attribute for the `img'
+  ;; markup, i.e. it is passed as the body of the `image' markup for
+  ;; non-Lout back-ends.
+
+  (define (file-contents file)
+    ;; Return the contents (a string) of file `file'.
+    (with-input-from-file file
+      (lambda ()
+	(let loop ((contents "")
+		   (line (read-line)))
+	  (if (eof-object? line)
+	      contents
+	      (loop (string-append contents line "\n")
+		    (read-line)))))))
+
+  (define (illustration-header)
+    ;; Return a string denoting the header of a Lout illustration.
+    (let ((lout (find-engine 'lout)))
+      (string-append "@SysInclude { picture }\n"
+		     (engine-custom lout 'includes)
+		     "\n\n@Illustration\n"
+		     "  @InitialFont { "
+		     (engine-custom lout 'initial-font)
+		     " }\n"
+		     "  @InitialBreak { "
+		     (engine-custom lout 'initial-break)
+		     " }\n"
+		     "  @InitialLanguage { "
+		     (engine-custom lout 'initial-language)
+		     " }\n"
+		     "  @InitialSpace { tex }\n"
+		     "{\n")))
+
+  (define (illustration-ending)
+    ;; Return a string denoting the end of a Lout illustration.
+    "\n}\n")
+
+  (let* ((opts (the-options args '(file ident alt)))
+	 (file* (assoc ':file opts))
+	 (ident* (assoc ':ident opts))
+	 (alt* (assoc ':alt opts))
+	 (file (and file* (cadr file*)))
+	 (ident (and ident* (cadr ident*)))
+	 (alt (or (and alt* (cadr alt*)) "An illustration")))
+
+    (let ((contents (if (not file)
+			(car (the-body args))
+			(file-contents file))))
+      (if (engine-format? "lout")
+	  (! contents) ;; simply inline the illustration
+	  (let* ((lout (find-engine 'lout))
+		 (output (string-append (or ident
+					    (symbol->string
+					     (gensym 'lout-illustration)))
+					".eps"))
+		 (port (open-output-pipe
+			(string-append (or (engine-custom lout
+							  'lout-program-name)
+					   "lout")
+				       " -o " output
+				       " -EPS"))))
+
+	    ;; send the illustration to Lout's standard input
+	    (display (illustration-header) port)
+	    (display contents port)
+	    (display (illustration-ending) port)
+
+	    (let ((exit-val (status:exit-val (close-pipe port))))
+	      (if (not (eqv? 0 exit-val))
+		  (skribe-error 'lout-illustration
+				"lout exited with error code" exit-val)))
+
+	    (if (not (file-exists? output))
+		(skribe-error 'lout-illustration "file not created"
+			      output))
+
+	    (let ((file-info (false-if-exception (stat output))))
+	      (if (or (not file-info)
+		      (= 0 (stat:size file-info)))
+		  (skribe-error 'lout-illustration
+				"empty output file" output)))
+
+	    ;; the image (FIXME: Should set its location)
+	    (image :file output alt))))))
+
+
+;*---------------------------------------------------------------------*/
+;*    Restore the base engine                                          */
+;*---------------------------------------------------------------------*/
+(pop-default-engine)
+
+
+;; Local Variables: --
+;; mode: Scheme --
+;; coding: latin-1 --
+;; scheme-program-name: "guile" --
+;; End: --
diff --git a/src/guile/skribilo/engine/xml.scm b/src/guile/skribilo/engine/xml.scm
new file mode 100644
index 0000000..81e9f27
--- /dev/null
+++ b/src/guile/skribilo/engine/xml.scm
@@ -0,0 +1,115 @@
+;;; xml.scm  --  Generic XML engine.
+;;;
+;;; Copyright 2003, 2004  Manuel Serrano
+;;;
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;; USA.
+
+(define-skribe-module (skribilo engine xml))
+
+;*---------------------------------------------------------------------*/
+;*    xml-engine ...                                                   */
+;*---------------------------------------------------------------------*/
+(define xml-engine
+   ;; setup the xml engine
+   (default-engine-set!
+      (make-engine 'xml
+		   :version 1.0
+		   :format "html"
+		   :delegate (find-engine 'base)
+		   :filter (make-string-replace '((#\< "&lt;")
+						  (#\> "&gt;")
+						  (#\& "&amp;")
+						  (#\" "&quot;")
+						  (#\@ "&#x40;"))))))
+
+;*---------------------------------------------------------------------*/
+;*    markup ...                                                       */
+;*---------------------------------------------------------------------*/
+(let ((xml-margin 0))
+   (define (make-margin)
+      (make-string xml-margin #\space))
+   (define (xml-attribute? val)
+      (cond
+	 ((or (string? val) (number? val) (boolean? val))
+	  #t)
+	 ((list? val)
+	  (every? xml-attribute? val))
+	 (else
+	  #f)))
+   (define (xml-attribute att val)
+      (let ((s (keyword->string att)))
+	 (printf " ~a=\"" (substring s 1 (string-length s)))
+	 (let loop ((val val))
+	    (cond
+	       ((or (string? val) (number? val))
+		(display val))
+	       ((boolean? val)
+		(display (if val "true" "false")))
+	       ((pair? val)
+		(for-each loop val))
+	       (else
+		#f)))
+	 (display #\")))
+   (define (xml-option opt val e)
+      (let* ((m (make-margin))
+	     (ks (keyword->string opt))
+	     (s (substring ks 1 (string-length ks))))
+	 (printf "~a<~a>\n" m s)
+	 (output val e)
+	 (printf "~a</~a>\n" m s)))
+   (define (xml-options n e)
+      ;; display the true options
+      (let ((opts (filter (lambda (o)
+			     (and (keyword? (car o))
+				  (not (xml-attribute? (cadr o)))))
+			  (markup-options n))))
+	 (if (pair? opts)
+	     (let ((m (make-margin)))
+		(display m)
+		(display "<options>\n")
+		(set! xml-margin (+ xml-margin 1))
+		(for-each (lambda (o)
+			     (xml-option (car o) (cadr o) e))
+			  opts)
+		(set! xml-margin (- xml-margin 1))
+		(display m)
+		(display "</options>\n")))))
+   (markup-writer #t
+      :options 'all
+      :before (lambda (n e)
+		 (printf "~a<~a" (make-margin) (markup-markup n))
+		 ;; display the xml attributes
+		 (for-each (lambda (o)
+			      (if (and (keyword? (car o))
+				       (xml-attribute? (cadr o)))
+				  (xml-attribute (car o) (cadr o))))
+			   (markup-options n))
+		 (set! xml-margin (+ xml-margin 1))
+		 (display ">\n"))
+      :action (lambda (n e)
+		 ;; options
+		 (xml-options n e)
+		 ;; body
+		 (output (markup-body n) e))
+      :after (lambda (n e)
+		(printf "~a</~a>\n" (make-margin) (markup-markup n))
+		(set! xml-margin (- xml-margin 1)))))
+
+;*---------------------------------------------------------------------*/
+;*    Restore the base engine                                          */
+;*---------------------------------------------------------------------*/
+(default-engine-set! (find-engine 'base))