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