dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

ox-epub.el (25414B)


      1 ;;; ox-epub.el --- Export org mode projects to EPUB -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (c) 2017 - Mark Meyer
      4 
      5 ;; Author: Mark Meyer <mark@ofosos.org>
      6 ;; Maintainer: Mark Meyer <mark@ofosos.org>
      7 
      8 ;; URL: http://github.com/ofosos/org-epub
      9 ;; Package-Version: 0.3
     10 ;; Package-Commit: 3d958203e169cbfb2204c43cb4c5543befec0b9d
     11 ;; Keywords: hypermedia
     12 
     13 ;; Version: 0.1.0
     14 
     15 ;; Package-Requires: ((emacs "24.3") (org "9"))
     16 
     17 ;; This program is free software; you can redistribute it and/or modify
     18 ;; it under the terms of the GNU General Public License as published by
     19 ;; the Free Software Foundation, either version 3 of the License, or
     20 ;; (at your option) any later version.
     21 
     22 ;; This program is distributed in the hope that it will be useful,
     23 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     24 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25 ;; GNU General Public License for more details.
     26 
     27 ;; You should have received a copy of the GNU General Public License
     28 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     29 
     30 ;;; Commentary:
     31 
     32 ;; This is an addition to the standard org-mode exporters.  The package
     33 ;; extends the (X)HTML exporter to produce EPUB files.  It eliminates
     34 ;; all inline CSS and JavaScript to accomplish this.  This exporter
     35 ;; will also tie the XHTML DTD to XHTML 1.1, a concrete DTD specifier
     36 ;; that was not supported by ox-html previously.
     37 
     38 ;; The main part is the generation of the table of contents in machine
     39 ;; readable form, as well as the spine, which defines the order in
     40 ;; which files are presented.  A lesser part is the inclusion of
     41 ;; various metadata properties, among them authorship and rights.
     42 
     43 ;;; Code:
     44 
     45 (require 'cl-lib)
     46 (require 'ox-publish)
     47 (require 'ox-html)
     48 (require 'org-element)
     49 
     50 (org-export-define-derived-backend 'epub 'html
     51   :options-alist
     52   '((:epub-uid "UID" nil nil t)
     53     (:epub-subject "Subject" nil nil t)
     54     (:epub-description "Description" nil nil t)
     55     (:epub-publisher "Publisher" nil nil t)
     56     (:epub-rights "License" nil nil t)
     57     (:epub-style "EPUBSTYLE" nil nil t)
     58     (:epub-cover "EPUBCOVER" nil nil t)
     59     (:html-doctype "HTML_DOCTYPE" nil "xhtml" t))
     60     
     61   :translate-alist
     62   '((template . org-epub-template)
     63     (link . org-epub-link)
     64     (latex-environment . org-epub--latex-environment)
     65     (latex-fragment . org-epub--latex-fragment))
     66   :menu-entry
     67   '(?E "Export to Epub"
     68        ((?e "As Epub file" org-epub-export-to-epub)
     69 	(?O "As Epub file and open"
     70 	    (lambda (a s v b)
     71 	      (if a (org-epub-export-to-epub t s v)
     72 		(org-open-file (org-epub-export-to-epub nil s v) 'system)))))))
     73 
     74 (defvar org-epub-zip-dir nil
     75   "The temporary directory to export to")
     76 
     77 (defvar org-epub-style-default "
     78   .title  { text-align: center;
     79              margin-bottom: .2em; }
     80   .subtitle { text-align: center;
     81               font-size: medium;
     82               font-weight: bold;
     83               margin-top:0; }
     84   .todo   { font-family: monospace; color: red; }
     85   .done   { font-family: monospace; color: green; }
     86   .priority { font-family: monospace; color: orange; }
     87   .tag    { background-color: #eee; font-family: monospace;
     88             padding: 2px; font-size: 80%; font-weight: normal; }
     89   .timestamp { color: #bebebe; }
     90   .timestamp-kwd { color: #5f9ea0; }
     91   .org-right  { margin-left: auto; margin-right: 0px;  text-align: right; }
     92   .org-left   { margin-left: 0px;  margin-right: auto; text-align: left; }
     93   .org-center { margin-left: auto; margin-right: auto; text-align: center; }
     94   .underline { text-decoration: underline; }
     95   #postamble p, #preamble p { font-size: 90%; margin: .2em; }
     96   p.verse { margin-left: 3%; }
     97   pre {
     98     border: 1px solid #ccc;
     99     box-shadow: 3px 3px 3px #eee;
    100     padding: 8pt;
    101     font-family: monospace;
    102     overflow: auto;
    103     margin: 1.2em;
    104   }
    105   pre.src {
    106     position: relative;
    107     overflow: visible;
    108     padding-top: 1.2em;
    109   }
    110 
    111   table { border-collapse:collapse; }
    112   caption.t-above { caption-side: top; }
    113   caption.t-bottom { caption-side: bottom; }
    114   td, th { vertical-align:top;  }
    115   th.org-right  { text-align: center;  }
    116   th.org-left   { text-align: center;   }
    117   th.org-center { text-align: center; }
    118   td.org-right  { text-align: right;  }
    119   td.org-left   { text-align: left;   }
    120   td.org-center { text-align: center; }
    121   dt { font-weight: bold; }
    122   .footpara { display: inline; }
    123   .footdef  { margin-bottom: 1em; }
    124   .figure { padding: 1em; }
    125   .figure p { text-align: center; }
    126   .inlinetask {
    127     padding: 10px;
    128     border: 2px solid gray;
    129     margin: 10px;
    130     background: #ffffcc;
    131   }
    132   #org-div-home-and-up
    133    { text-align: right; font-size: 70%; white-space: nowrap; }
    134   textarea { overflow-x: auto; }
    135   .linenr { font-size: smaller }
    136   .code-highlighted { background-color: #ffff00; }
    137   .org-info-js_info-navigation { border-style: none; }
    138   #org-info-js_console-label
    139     { font-size: 10px; font-weight: bold; white-space: nowrap; }
    140   .org-info-js_search-highlight
    141     { background-color: #ffff00; color: #000000; font-weight: bold; }
    142   .org-svg { width: 90%; }
    143 
    144 "
    145   "Default style declarations for org epub")
    146 
    147 (defvar org-epub-zip-command "zip"
    148   "Command to call to create zip files.")
    149 
    150 (defvar org-epub-zip-no-compress (list "-Xu0")
    151   "Zip command option list to pass for no compression.")
    152 
    153 (defvar org-epub-zip-compress (list "-Xu9")
    154   "Zip command option list to pass for compression.")
    155 
    156 (defvar org-epub-metadata nil
    157   "EPUB export metadata")
    158 
    159 (defvar org-epub-headlines nil
    160   "EPUB headlines")
    161 
    162 (defvar org-epub-style-counter 0
    163   "EPUB style counter")
    164 
    165 ;; manifest mechanism
    166 
    167 (defvar org-epub-manifest nil
    168   "EPUB export manifest")
    169 
    170 (defun org-epub-manifest-entry (id filename type mimetype &optional source)
    171   "Create a manifest entry with the given ID, FILENAME, TYPE, MIMETYPE and optional SOUCE.
    172 
    173 FILENAME should be the new name in the epub container. TYPE
    174 should be one of `'html', `'stylesheet', `'coverimg', `'cover' or
    175 `'img'. If SOURCE is given the file name by SOUCE will be copied
    176 to FILENAME at the end of the export process.  "
    177   (list :id id :filename filename :type type :mimetype mimetype :source source))
    178 
    179 (defun org-epub-cover-p (manifest-entry)
    180   "Determine if MANIFEST-ENTRY is of type cover."
    181   (eq (plist-get manifest-entry :type) 'cover))
    182 
    183 (defun org-epub-coverimg-p (manifest-entry)
    184   "Determine if MANIFEST-ENTRY is of type cover image."
    185   (eq (plist-get manifest-entry :type) 'coverimg))
    186 
    187 (defun org-epub-style-p (manifest-entry)
    188   "Determine if MANIFEST-ENTRY is of type stylesheet."
    189   (eq (plist-get manifest-entry :type) 'stylesheet))
    190 
    191 (defun org-epub-manifest-needcopy (manifest-entry)
    192   "Determine if MANIFEST-ENTRY needs to be copied.
    193 
    194 If it needs to be copied return a pair (sourcefile . targetfile)."
    195   (if (plist-get manifest-entry :source)
    196       (cons (plist-get manifest-entry :source)
    197 	    (plist-get manifest-entry :filename))
    198     nil))
    199 
    200 (defun org-epub-manifest-all (pred)
    201   "Return all manifest entries for which PRED is true."
    202   (cl-remove-if-not pred org-epub-manifest))
    203 
    204 (cl-defun org-epub-manifest-first (pred)
    205   "Return the first manifest entry for which PRED is true."
    206   (let ((val))
    207     (dolist (el org-epub-manifest val)
    208       (when (funcall pred el)
    209 	(cl-return-from org-epub-manifest-first el)))))
    210 
    211 ;; core
    212 
    213 ;;; Latex Environment - stolen from ox-html
    214 
    215 (defun org-epub--latex-environment (latex-environment _contents info)
    216   "Transcode a LATEX-ENVIRONMENT element from Org to HTML.
    217 CONTENTS is nil.  INFO is a plist holding contextual information."
    218   (let ((processing-type (plist-get info :with-latex))
    219 	(latex-frag (org-remove-indentation
    220 		     (org-element-property :value latex-environment)))
    221 	(attributes (org-export-read-attribute :attr_html latex-environment)))
    222     (cond
    223      ((assq processing-type org-preview-latex-process-alist)
    224       (let ((formula-link
    225 	     (org-html-format-latex latex-frag processing-type info)))
    226 	(when (and formula-link (string-match "file:\\([^]]*\\)" formula-link))
    227 	  ;; Do not provide a caption or a name to be consistent with
    228 	  ;; `mathjax' handling.
    229 	  (org-html--wrap-image
    230 	   (org-html--format-image
    231 	    (let* ((path (match-string 1 formula-link))
    232 		   (ref (org-export-get-reference latex-environment info))
    233 		   (mime (file-name-extension path))
    234 		   (name (concat "img-" ref "." mime)))
    235 	      (message "Formatting Latex environment: %s" name)
    236 	      (push (org-epub-manifest-entry ref name 'img (concat "image/" mime) path) org-epub-manifest)
    237 	      name) attributes info) info))))
    238      (t latex-frag))))
    239 
    240 ;;;; Latex Fragment - stolen from ox-html
    241 
    242 (defun org-epub--latex-fragment (latex-fragment _contents info)
    243   "Transcode a LATEX-FRAGMENT object from Org to HTML.
    244 CONTENTS is nil.  INFO is a plist holding contextual information."
    245   (let ((latex-frag (org-element-property :value latex-fragment))
    246 	(processing-type (plist-get info :with-latex)))
    247     (cond
    248      ((assq processing-type org-preview-latex-process-alist)
    249       (let ((formula-link
    250 	     (org-html-format-latex latex-frag processing-type info)))
    251 	(when (and formula-link (string-match "file:\\([^]]*\\)" formula-link))
    252 	  (let* ((path (match-string 1 formula-link))
    253 		 (ref (org-export-get-reference latex-fragment info))
    254 		 (mime (file-name-extension path))
    255 		 (name (concat "img-" ref "." mime)))
    256 	    (message "Formatting Latex fragement: %s" name)
    257 	    (push (org-epub-manifest-entry ref name 'img (concat "image/" mime) path) org-epub-manifest)
    258 	    (org-html--format-image name nil info)))))
    259      (t latex-frag))))
    260 
    261 
    262 (defun org-epub-link (link desc info)
    263   "Return the HTML required for a link descriped by LINK, DESC, and INFO.
    264 
    265 See org-html-link for more info."
    266   (when (and (not desc) (org-export-inline-image-p link (plist-get info :html-inline-image-rules)))
    267     (let* ((path (org-element-property :path link))
    268 	   (ref (org-export-get-reference link info))
    269 	   (mime (file-name-extension path))
    270 	   (name (concat "img-" ref "." mime)))
    271       (push (org-epub-manifest-entry ref name 'img (concat "image/" mime) path) org-epub-manifest)
    272       (org-element-put-property link :path name)))
    273   (org-html-link link desc info))
    274 
    275 (defun org-epub-meta-put (symbols info)
    276   "Put SYMBOLS taken from INFO into the org-epub metadata cache."
    277   (mapc
    278    #'(lambda (sym)
    279        (let ((data (plist-get info sym)))
    280 	 (setq org-epub-metadata
    281 	       (plist-put org-epub-metadata sym
    282 			  (if (listp data)
    283 			      (org-export-data data info)
    284 			    data)))))
    285    symbols))
    286 
    287 (defun org-epub-template (contents info)
    288   "Return complete document string after HTML conversion.
    289 CONTENTS is the transcoded contents string.  INFO is a plist
    290 holding export options."
    291   (org-epub-meta-put '(:epub-uid :title :language :epub-subject :epub-description :author
    292 				 :epub-publisher :date :epub-rights :html-head-include-default-style :epub-cover :epub-style) info)
    293   (setq org-epub-metadata (plist-put org-epub-metadata :epub-toc-depth 2))
    294   ;; maybe set toc-depth "2" to some dynamic value
    295   (setq org-epub-headlines
    296 	(mapcar (lambda (headline)
    297 		  (list
    298 		   (org-element-property :raw-value headline)
    299 		   (org-element-property :level headline)
    300 		   (org-export-get-reference headline info)))
    301 		(org-export-collect-headlines info 2)))
    302   (let ((styles (split-string (or (plist-get org-epub-metadata :epub-style) " "))))
    303     (mapc #'(lambda (style)
    304 	      (let* ((stylenum (cl-incf org-epub-style-counter))
    305 		     (stylename (concat "style-" (format "%d" stylenum)))
    306 		     (stylefile (concat stylename ".css")))
    307 		(push (org-epub-manifest-entry stylename stylefile 'stylesheet "text/css" style) org-epub-manifest)))
    308 	  styles))
    309   (concat
    310    (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info))
    311      (let* ((xml-declaration (plist-get info :html-xml-declaration))
    312 	    (decl (or (and (stringp xml-declaration) xml-declaration)
    313 		      (cdr (assoc (plist-get info :html-extension)
    314 				  xml-declaration))
    315 		      (cdr (assoc "html" xml-declaration))
    316 		      "")))
    317        (when (not (or (not decl) (string= "" decl)))
    318 	 (format "%s\n"
    319 		 (format decl
    320 			 (or (and org-html-coding-system
    321 				  (fboundp 'coding-system-get)
    322 				  (coding-system-get org-html-coding-system 'mime-charset))
    323 			     "iso-8859-1"))))))
    324    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
    325    "\n"
    326    (concat "<html"
    327 	   (format
    328 	    " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\""
    329 	    (plist-get info :language) (plist-get info :language))
    330 	   ">\n")
    331    
    332    "<head>\n"
    333    (org-html--build-meta-info info)
    334    (when (plist-get info :html-head-include-default-style)
    335      "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/>\n")
    336    (when (plist-get info :epub-style)
    337      (mapconcat
    338       #'(lambda (entry)
    339 	  (concat "<link rel=\"stylesheet\" type=\"text/css\" href=\"" (plist-get entry :filename) "\"/>\n"))
    340       (org-epub-manifest-all #'org-epub-style-p) "\n"))
    341    "</head>\n"
    342    "<body>\n"
    343    ;; Preamble.
    344    (org-html--build-pre/postamble 'preamble info)
    345    ;; Document contents.
    346 					;   (let ((div (assq 'content (plist-get info :html-divs))))
    347 					;     (format "<%s id=\"%s\">\n" (nth 1 div) (nth 2 div)))
    348    "<div id=\"content\">"
    349    ;; Document title.
    350    (when (plist-get info :with-title)
    351      (let ((ftitle (plist-get info :title))
    352 	   (subtitle (plist-get info :subtitle)))
    353        (when ftitle
    354 	 (message (org-export-data ftitle info))
    355 	 (format
    356 	  "<h1 class=\"title\">%s</h1>%s\n"
    357 	  (org-export-data ftitle info)
    358 	  (if subtitle
    359 	      (format
    360 	       "<p class=\"subtitle\">%s</p>\n"
    361 	       (org-export-data subtitle info))
    362 	    "")))))
    363      contents
    364      "</div>"
    365      ;   (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs))))
    366      ;; Postamble.
    367      (org-html--build-pre/postamble 'postamble info)
    368      ;; Closing document.
    369      "</body>\n</html>"))
    370 
    371 ;; see ox-odt
    372 
    373 (defmacro org-epub--export-wrapper (outfile &rest body)
    374   "Export an Epub with BODY generating the main html file and OUTFILE as target file."
    375   `(let* ((outfile ,outfile)
    376 	      (org-epub-manifest nil)
    377 	      (org-epub-metadata nil)
    378 	      (org-epub-style-counter 0)
    379 	      (out-file-type (file-name-extension outfile))
    380 	      (org-epub-zip-dir (file-name-as-directory
    381 				 (make-temp-file (format "%s-" out-file-type) t)))
    382 	      (body ,@body))
    383      (condition-case err
    384 	 (progn
    385 	   (when (plist-get org-epub-metadata :html-head-include-default-style)
    386 	     (with-current-buffer (find-file (concat org-epub-zip-dir "style.css"))
    387 	       (insert org-epub-style-default)
    388 	       (save-buffer 0)
    389 	       (kill-buffer)
    390 	       (push (org-epub-manifest-entry "default-style" "style.css" 'stylesheet "text/css") org-epub-manifest)))
    391 	   (when (org-string-nw-p (plist-get org-epub-metadata :epub-cover))
    392 	     (let* ((cover-path (plist-get org-epub-metadata :epub-cover))
    393 		    (cover-type (file-name-extension cover-path))
    394 		    (cover-img (create-image (expand-file-name cover-path)))
    395 		    (cover-width (car (image-size cover-img t)))
    396 		    (cover-height (cdr (image-size cover-img t)))
    397 		    (cover-name (concat "cover." cover-type)))
    398 	       (with-current-buffer (find-file (concat org-epub-zip-dir "cover.html"))
    399 		 (erase-buffer)
    400 		 (insert
    401 		  (org-epub-template-cover cover-name cover-width cover-height))
    402 		 (save-buffer 0)
    403 		 (kill-buffer)
    404 		 (let ((men (org-epub-manifest-entry "cover" "cover.html" 'cover "application/xhtml+xml")))
    405 		   (push men org-epub-manifest))
    406 		 (let ((men (org-epub-manifest-entry "cover-image" cover-name 'coverimg (concat "image/" cover-type) cover-path)))
    407 		   (push men org-epub-manifest)))))
    408            (unless (file-directory-p (expand-file-name "META-INF" org-epub-zip-dir))
    409              (make-directory (file-name-as-directory (expand-file-name "META-INF" org-epub-zip-dir))))
    410 	   (with-current-buffer (find-file (expand-file-name "META-INF/container.xml" org-epub-zip-dir))
    411 	     (erase-buffer)
    412 	     (insert (org-epub-template-container))
    413 	     (save-buffer 0)
    414 	     (kill-buffer))
    415 	   (with-current-buffer (find-file (concat org-epub-zip-dir "mimetype"))
    416 	     (erase-buffer)
    417 	     (insert (org-epub-template-mimetype))
    418 	     (save-buffer 0)
    419 	     (kill-buffer))
    420 	   (with-current-buffer (find-file (concat org-epub-zip-dir "body.html"))
    421 	     (erase-buffer)
    422 	     (insert body)
    423 	     (save-buffer 0)
    424 	     (kill-buffer)
    425 	     (nconc org-epub-manifest (list (org-epub-manifest-entry "body-html" "body.html" 'html "application/xhtml+xml"))))
    426 	   (with-current-buffer (find-file (concat org-epub-zip-dir "toc.ncx"))
    427 	     (erase-buffer)
    428 	     (insert
    429 	      (org-epub-template-toc-ncx
    430 	       (plist-get org-epub-metadata :epub-uid)
    431 	       (plist-get org-epub-metadata :epub-toc-depth)
    432 	       (plist-get org-epub-metadata :title)
    433 	       (org-epub-generate-toc-single org-epub-headlines "body.html")))
    434 	     (save-buffer 0)
    435 	     (kill-buffer))
    436 	   (with-current-buffer (find-file (concat org-epub-zip-dir "content.opf"))
    437 	     (erase-buffer)
    438 	     (insert (org-epub-template-content-opf
    439 		      org-epub-metadata
    440 		      (org-epub-gen-manifest org-epub-manifest)
    441 		      (org-epub-gen-spine '(("body-html" . "body.html")))))
    442 	     (save-buffer 0)
    443 	     (kill-buffer))
    444 	   (org-epub-zip-it-up outfile org-epub-manifest org-epub-zip-dir)
    445 	   (delete-directory org-epub-zip-dir t)
    446 	   (message (with-output-to-string (print org-epub-manifest)))
    447 	   (message "Generated %s" outfile)
    448 	   (expand-file-name outfile))
    449        (error (delete-directory org-epub-zip-dir t)
    450 	      (message "ox-epub eport error: %s" err)))))
    451 
    452 ;;compare org-export-options-alist
    453 ;;;###autoload
    454 (defun org-epub-export-to-epub (&optional async subtreep visible-only ext-plist)
    455   "Export the current buffer to an EPUB file.
    456 
    457 ASYNC defines wether this process should run in the background,
    458 SUBTREEP supports narrowing of the document, VISIBLE-ONLY allows
    459 you to export only visible parts of the document, EXT-PLIST is
    460 the property list for the export process."
    461   (interactive)
    462   (let* ((outfile (org-export-output-file-name ".epub" subtreep)))
    463     (message "Output to:")
    464     (message outfile)
    465     (if async
    466 	(org-export-async-start (lambda (f) (org-export-add-to-stack f 'odt))
    467 	  (org-epub--export-wrapper
    468 	   outfile
    469 	   (org-export-as 'epub subtreep visible-only nil ext-plist)))
    470       (org-epub--export-wrapper
    471        outfile
    472        (org-export-as 'epub subtreep visible-only nil ext-plist)))))
    473 
    474 (defun org-epub-template-toc-ncx (uid toc-depth title toc-nav)
    475   "Create the toc.ncx file.
    476 
    477 UID is the uid/url of the file.  TOC-DEPTH is the depth of the toc
    478 that should be shown to the readers.  TITLE is the title of the
    479 ebook and TOC-NAV being the raw contents enclosed in navMap."
    480   (concat
    481    "<?xml version=\"1.0\"?>
    482 <!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"
    483    \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">
    484 
    485 <ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">
    486 
    487    <head>
    488       <meta name=\"dtb:uid\" content=\""
    489    uid
    490    "\"/>
    491       <meta name=\"dtb:depth\" content=\""
    492    (format "%d" toc-depth)
    493    "\"/>
    494       <meta name=\"dtb:totalPageCount\" content=\"0\"/>
    495       <meta name=\"dtb:maxPageNumber\" content=\"0\"/>
    496    </head>
    497 
    498    <docTitle>
    499       <text>"
    500    title
    501    "</text>
    502    </docTitle>
    503 
    504    <navMap>"
    505    toc-nav
    506    "</navMap>
    507 </ncx>"))
    508 
    509 (defun org-epub-template-content-opf (meta manifest spine)
    510   "Create the content.opf file.
    511 
    512 META is a metadata PLIST.
    513 
    514 The following arguments are XML strings: MANIFEST is the content
    515 inside the manifest tags, this should include all user generated
    516 html files but not things like the cover page, SPINE is an XML
    517 string with the list of html files in reading order."
    518   (concat
    519    "<?xml version=\"1.0\"?>
    520 
    521 <package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"dcidid\"
    522    version=\"2.0\">
    523 
    524    <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
    525       xmlns:dcterms=\"http://purl.org/dc/terms/\"
    526       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
    527       xmlns:opf=\"http://www.idpf.org/2007/opf\">
    528       <dc:title>" (plist-get meta :title) "</dc:title>
    529       <dc:language xsi:type=\"dcterms:RFC3066\">" (plist-get meta :language) "</dc:language>
    530       <dc:identifier id=\"dcidid\" opf:scheme=\"URI\">"
    531       (plist-get meta :epub-uid)
    532          "</dc:identifier>
    533       <dc:subject>" (plist-get meta :epub-subject)
    534          "</dc:subject>
    535       <dc:description>" (plist-get meta :epub-description)
    536 
    537          "</dc:description>
    538       <dc:creator>" (plist-get meta :author) "</dc:creator>
    539       <dc:publisher>" (plist-get meta :epub-publisher) "</dc:publisher>
    540       <dc:date xsi:type=\"dcterms:W3CDTF\">" (plist-get meta :date) "</dc:date>
    541       <dc:rights>" (plist-get meta :epub-rights) "</dc:rights>"
    542       (let ((cimg (org-epub-manifest-first #'org-epub-coverimg-p)))
    543 	(when cimg
    544 	  (concat "<meta name=\"cover\" content=\"" (plist-get cimg :id) "\"/>")))
    545       "
    546    </metadata>
    547 
    548    <manifest>\n
    549       <item id=\"ncx\"      href=\"toc.ncx\"
    550          media-type=\"application/x-dtbncx+xml\" />"
    551       
    552       manifest
    553       
    554    "</manifest>
    555 
    556    <spine toc=\"ncx\">"
    557    (let ((chtml (org-epub-manifest-first #'org-epub-cover-p)))
    558      (when chtml
    559        (concat "<itemref idref=\"" (plist-get chtml :id) "\" linear=\"no\" />")))
    560    
    561    spine
    562 
    563    "</spine>
    564 
    565  <guide>"
    566    (let ((chtml (org-epub-manifest-first #'org-epub-cover-p)))
    567      (when chtml
    568        (concat " <reference type=\"cover\" href=\"" (plist-get chtml :filename) "\" />")))
    569    "
    570  </guide>
    571 
    572 </package>"))
    573 
    574 (defun org-epub-gen-manifest (files)
    575   "Generate the manifest XML string.
    576 
    577 FILES is the list of files to be included in the manifest xml string."
    578   (mapconcat
    579    (lambda (file)
    580      (concat "<item id=\"" (plist-get file :id) "\"      href=\"" (plist-get file :filename) "\"
    581             media-type=\"" (plist-get file :mimetype) "\" />\n"))
    582    files ""))
    583 
    584 (defun org-epub-gen-spine (files)
    585   "Generate the spine XML string.
    586 
    587 FILES is the list of files to be included in the spine, these
    588 must be in reading order."
    589   (mapconcat
    590    (lambda (file)
    591      (concat "<itemref idref=\"" (car file) "\" />\n"))
    592    files ""))
    593 
    594 (defun org-epub-template-container ()
    595   "Generate the container.xml file, the root of any EPUB."
    596   "<?xml version=\"1.0\"?>
    597 <container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">
    598    <rootfiles>
    599       <rootfile full-path=\"content.opf\"
    600       media-type=\"application/oebps-package+xml\"/>
    601    </rootfiles>
    602 </container>")
    603 
    604 (defun org-epub-template-cover (cover-file width height)
    605   "Generate a HTML template for the cover page.
    606 
    607 COVER-FILE is the filename of a jpeg file, while WIDTH and HEIGHT are
    608 properties of the image."
    609    (concat "<?xml version=\"1.0\" encoding=\"utf-8\"?>
    610  <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">
    611  
    612  <html xmlns=\"http://www.w3.org/1999/xhtml\">
    613  <head>
    614  <title></title>
    615  </head>
    616  
    617  <body>
    618  <svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"
    619   width=\"100%\" height=\"100%\" viewBox=\"0 0 573 800\" preserveAspectRatio=\"xMidYMid meet\">
    620  <image xlink:href=\"" cover-file "\" height=\"" (format "%d" height) "\" width=\"" (format "%d" width) "\" />
    621  </svg>
    622  </body>
    623  </html>"))
    624 
    625 (defun org-epub-template-mimetype ()
    626   "Generate the mimetype file for the epub."
    627   "application/epub+zip")
    628 
    629 (defun org-epub-zip-it-up (epub-file files target-dir)
    630   "Create the .epub file by zipping up the contents.
    631 
    632 EPUB-FILE is the target filename, FILES is the list of source
    633 files to process, while TARGET-DIR is the directory where
    634 exported HTML files live. This function will copy any files into
    635 their proper place."
    636   (mapc #'(lambda (entry)
    637 	    (let ((copy (org-epub-manifest-needcopy entry)))
    638 	      (when copy
    639 		(copy-file (car copy) (concat target-dir (cdr copy)) t))))
    640 	files)
    641   (let ((default-directory target-dir)
    642 	(meta-files '("META-INF/container.xml" "content.opf" "toc.ncx")))
    643     (apply 'call-process
    644 	   (append (list org-epub-zip-command nil '(:file "zip.log") nil)
    645 		   org-epub-zip-no-compress
    646 		   (list epub-file
    647 			 "mimetype")))
    648     (apply 'call-process org-epub-zip-command nil '(:file "zip.log") nil
    649 	   (append org-epub-zip-compress
    650 		   (list epub-file)
    651 		   (append meta-files (mapcar #'(lambda (el) (plist-get el :filename)) files)))))
    652   (copy-file (concat target-dir epub-file) default-directory t))
    653 
    654 (defun org-epub-generate-toc-single (headlines filename)
    655   "Generate a single file TOC.
    656 
    657 HEADLINES is a list containing the abbreviated headline
    658 information. The name of the target file is given by FILENAME."
    659   (let ((toc-id 0)
    660 	(current-level 0))
    661     (with-output-to-string
    662       (mapc
    663        (lambda (headline)
    664 	 (let* ((title (nth 0 headline))
    665 		(level (nth 1 headline))
    666 		(ref (nth 2 headline)))
    667 	   (cl-incf toc-id)
    668 	   (cond
    669 	    ((< current-level level)
    670 	     (cl-incf current-level))
    671 	    ((> current-level level)
    672 	     (princ "</navPoint>")
    673 	     (while (> current-level level)
    674 	       (cl-decf current-level)
    675 	       (princ "</navPoint>")))
    676 	    ((eq current-level level)
    677 	     (princ "</navPoint>")))
    678 	   (princ
    679 	    (concat (format "<navPoint class=\"h%d\" id=\"%s-%d\">\n" current-level filename toc-id)
    680 		    (format "<navLabel><text>%s</text></navLabel>\n" (org-html-encode-plain-text title))
    681 		    (format "<content src=\"%s#%s\"/>" filename ref)))))
    682        headlines)
    683       (while (> current-level 0)
    684 	(princ "</navPoint>")
    685 	(cl-decf current-level)))))
    686 
    687 (provide 'ox-epub)
    688 
    689 ;;; ox-epub.el ends here