summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/output/base.js18
-rw-r--r--lib/output/ebook.js145
-rw-r--r--lib/utils/command.js22
-rw-r--r--package.json2
4 files changed, 181 insertions, 6 deletions
diff --git a/lib/output/base.js b/lib/output/base.js
index 4190559..828a11e 100644
--- a/lib/output/base.js
+++ b/lib/output/base.js
@@ -7,7 +7,6 @@ var pathUtil = require('../utils/path');
var location = require('../utils/location');
var PluginsManager = require('../plugins');
var TemplateEngine = require('../template');
-var gitbook = require('../gitbook');
/*
Output is like a stream interface for a parsed book
@@ -16,9 +15,13 @@ to output "something".
The process is mostly on the behavior of "onPage" and "onAsset"
*/
-function Output(book) {
+function Output(book, opts) {
_.bindAll(this);
+ this.opts = _.defaults(opts || {}, {
+
+ });
+
this.book = book;
this.log = this.book.log;
@@ -175,6 +178,17 @@ Output.prototype.onResolveTemplate = function(from, to) {
// ---- Utilities ----
+// Return a default context for templates
+Output.prototype.getContext = function() {
+ return _.extend(
+ {},
+ this.book.getContext(),
+ this.book.summary.getContext(),
+ this.book.glossary.getContext(),
+ this.book.config.getContext()
+ );
+};
+
// Resolve a file path in the context of a specific page
// Result is an "absolute path relative to the output folder"
Output.prototype.resolveForPage = function(page, href) {
diff --git a/lib/output/ebook.js b/lib/output/ebook.js
index 7e7a66d..a0e72a9 100644
--- a/lib/output/ebook.js
+++ b/lib/output/ebook.js
@@ -1,14 +1,155 @@
+var _ = require('lodash');
var util = require('util');
+var juice = require('juice');
+var command = require('../utils/command');
+var fs = require('../utils/fs');
var WebsiteOutput = require('./website');
var assetsInliner = require('./assets-inliner');
-function EbookOutput() {
+function _EbookOutput() {
WebsiteOutput.apply(this, arguments);
}
util.inherits(EbookOutput, WebsiteOutput);
+var EbookOutput = assetsInliner(_EbookOutput);
+
EbookOutput.prototype.name = 'ebook';
+// Finish generation, create ebook using ebook-convert
+EbookOutput.prototype.finish = function() {
+ var that = this;
+
+ return Promise()
+ .then(function() {
+ return EbookOutput.super_.prototype.finish.apply(that);
+ })
+
+ // Generate SUMMARY.html
+ .then(function() {
+ return that.render('summary', that.getContext())
+ .then(function(html) {
+ return that.writeFile(
+ that.resolve('SUMMARY.html'),
+ html
+ );
+ });
+ })
+
+ // Start ebook-convert
+ .then(function() {
+ return that.ebookConvertOption();
+ })
+
+ .then(function(options) {
+ var cmd = [
+ 'ebook-convert',
+ that.resolve('SUMMARY.html'),
+ that.resolve('index.'+that.opts.format),
+ command.optionsToShellArgs(options)
+ ].join(' ');
+
+ return command.exec(cmd)
+ .fail(function(err) {
+ if (err.code == 127) {
+ err = new Error('Need to install ebook-convert from Calibre');
+ }
+
+ throw err;
+ });
+ });
+};
+
+// Generate header/footer for PDF
+EbookOutput.prototype.getPDFTemplate = function(tpl) {
+ var that = this;
+ var context = _.extend(
+ {
+ // Nunjucks context mapping to ebook-convert templating
+ page: {
+ num: '_PAGENUM_',
+ title: '_TITLE_'
+ }
+ },
+ this.getContext()
+ );
+
+ return this.render('pdf_'+tpl, context)
+
+ // Inline css, include css relative to the output folder
+ .then(function(output) {
+ return Promise.nfcall(juice.juiceResources, tpl, {
+ webResources: {
+ relativeTo: that.root()
+ }
+ });
+ });
+};
+
+// Generate options for ebook-convert
+EbookOutput.prototype.ebookConvertOption = function() {
+ var that = this;
+ var cover = this.book.config.get('cover');
+
+ if (!cover && fs.existsSync(this.resolve('cover.jpg'))) {
+ cover = this.resolve('cover.jpg');
+ }
+
+ var options = {
+ '--cover': cover,
+ '--title': this.book.config.get('title'),
+ '--comments': this.book.config.get('description'),
+ '--isbn': this.book.config.get('isbn'),
+ '--authors': this.book.config.get('author'),
+ '--language': this.book.config.get('language'),
+ '--book-producer': 'GitBook',
+ '--publisher': 'GitBook',
+ '--chapter': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter \')]',
+ '--level1-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-1 \')]',
+ '--level2-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-2 \')]',
+ '--level3-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-3 \')]',
+ '--no-chapters-in-toc': true,
+ '--max-levels': '1',
+ '--breadth-first': true
+ };
+
+ if (this.opts.format == 'epub') {
+ options = _.extend(options, {
+ '--dont-split-on-page-breaks': true
+ });
+ }
+
+ if (this.opts.format != 'pdf') return Promise(options);
+
+ var pdfOptions = this.book.config.get('pdf');
+
+ options = _.extend(options, {
+ '--chapter-mark': String(pdfOptions.chapterMark),
+ '--page-breaks-before': String(pdfOptions.pageBreaksBefore),
+ '--margin-left': String(pdfOptions.margin.left),
+ '--margin-right': String(pdfOptions.margin.right),
+ '--margin-top': String(pdfOptions.margin.top),
+ '--margin-bottom': String(pdfOptions.margin.bottom),
+ '--pdf-default-font-size': String(pdfOptions.fontSize),
+ '--pdf-mono-font-size': String(pdfOptions.fontSize),
+ '--paper-size': String(pdfOptions.paperSize),
+ '--pdf-page-numbers': Boolean(pdfOptions.pageNumbers),
+ '--pdf-header-template': that.getPDFTemplate('header'),
+ '--pdf-footer-template': that.getPDFTemplate('footer'),
+ '--pdf-sans-family': String(pdfOptions.fontFamily)
+ });
+
+ return this.getPDFTemplate('header')
+ .then(function(tpl) {
+ options['--pdf-header-template'] = tpl;
+
+ return that.getPDFTemplate('footer');
+ })
+ .then(function(tpl) {
+ options['--pdf-footer-template'] = tpl;
+
+ return options;
+ });
+};
-module.exports = assetsInliner(EbookOutput);
+module.exports = EbookOutput;
diff --git a/lib/utils/command.js b/lib/utils/command.js
index c3e33c7..49b0998 100644
--- a/lib/utils/command.js
+++ b/lib/utils/command.js
@@ -1,3 +1,4 @@
+var _ = require('lodash');
var childProcess = require('child_process');
var Promise = require('./promise');
@@ -37,8 +38,27 @@ function spawn(command, args, options) {
return d.promise;
}
+// Transform an option object to a command line string
+function escapeShellArg(s) {
+ s = s.replace(/"/g, '\\"');
+ return '"' + s + '"';
+}
+
+function optionsToShellArgs(options) {
+ return _.chain(options)
+ .map(function(value, key) {
+ if (value === null || value === undefined || value === false) return null;
+ if (value === true) return key;
+ return key + '=' + escapeShellArg(value);
+ })
+ .compact()
+ .value()
+ .join(' ');
+}
+
module.exports = {
isAvailable: isAvailable,
exec: exec,
- spawn: spawn
+ spawn: spawn,
+ optionsToShellArgs: optionsToShellArgs
};
diff --git a/package.json b/package.json
index 474effa..2710dde 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
"dom-serializer": "0.1.0",
"spawn-cmd": "0.0.2",
"escape-string-regexp": "1.0.3",
- "juice": "1.5.0",
+ "juice": "1.9.0",
"jsonschema": "1.0.2",
"json-schema-defaults": "0.1.1",
"merge-defaults": "0.2.1",