afterwriting
Version:
Post-processing tools for Fountain screenplays
506 lines (430 loc) • 19.6 kB
JavaScript
define('utils/pdfmaker', function(require) {
// TODO: Extract pdfmaker to a separate library (+++++)
var PDFDocument = require('pdfkit'),
helper = require('utils/helper');
var module = {};
var create_simplestream = function(filepath) {
var simplestream = {
chunks: [],
filepath: filepath
};
simplestream.on = function(event, callback) {
this.callback = callback;
};
simplestream.once = function() {
};
simplestream.emit = function() {
};
simplestream.write = function(chunk) {
this.chunks.push(chunk);
};
simplestream.end = function() {
if (simplestream.filepath) {
var fsmodule = 'fs';
var fs = require(fsmodule); // bypass requirejs minification/optimization
var stream = fs.createWriteStream(simplestream.filepath, {
encoding: "binary"
});
stream.on('finish', this.callback);
simplestream.chunks.forEach(function(buffer) {
stream.write(Buffer.from(buffer.toString('base64'), 'base64'));
});
stream.end();
} else {
simplestream.blob = new Blob(simplestream.chunks, {
type: "application/pdf"
});
simplestream.url = URL.createObjectURL(this.blob);
this.callback(simplestream);
}
};
return simplestream;
};
function initDoc(opts) {
var print = opts.print;
var fonts = opts.config.fonts || null;
var options = {
compress: false,
size: print.paper_size === "a4" ? 'A4' : 'LETTER',
margins: {
top: 0,
left: 0,
bottom: 0,
right: 0
}
};
var doc = new PDFDocument(options);
if (opts.config.fonts) {
doc.registerFont('ScriptNormal', fonts.regular);
doc.registerFont('ScriptBold', fonts.bold);
doc.registerFont('ScriptBoldOblique', fonts.bolditalic);
doc.registerFont('ScriptOblique', fonts.italic);
}
else {
doc.registerFont('ScriptNormal', 'Courier');
doc.registerFont('ScriptBold', 'Courier-Bold');
doc.registerFont('ScriptBoldOblique', 'Courier-BoldOblique');
doc.registerFont('ScriptOblique','Courier-Oblique');
}
doc.font('ScriptNormal');
doc.fontSize(print.font_size || 12);
// convert points to inches for text
doc.reset_format = function() {
doc.format_state = {
bold: false,
italic: false,
underline: false,
override_color: null
};
};
doc.reset_format();
var inner_text = doc.text;
doc.simple_text = function() {
doc.font('ScriptNormal');
inner_text.apply(doc, arguments);
};
doc.format_text = function(text, x, y, options) {
var cache_current_state = doc.format_state;
doc.reset_format();
doc.text(text, x, y, options);
doc.format_state = cache_current_state;
};
doc.text = function(text, x, y, options) {
options = options || {};
var color = options.color || 'black';
color = doc.format_state.override_color ? doc.format_state.override_color : color;
doc.fill(color);
if (print.note.italic) {
text = text.replace(/\[\[/g, '*[[').replace(/\]\]/g, ']]*');
}
var split_for_fromatting = text.split(/(\\\*)|(\*{1,3})|(\\?_)|(\[\[)|(\]\])/g).filter(function(a) {
return a;
});
var font_width = print.font_width;
for (var i = 0; i < split_for_fromatting.length; i++) {
var elem = split_for_fromatting[i];
if (elem === '***') {
doc.format_state.italic = !doc.format_state.italic;
doc.format_state.bold = !doc.format_state.bold;
} else if (elem === '**') {
doc.format_state.bold = !doc.format_state.bold;
} else if (elem === '*') {
doc.format_state.italic = !doc.format_state.italic;
} else if (elem === '_') {
doc.format_state.underline = !doc.format_state.underline;
} else if (elem === '[[') {
doc.format_state.override_color = (print.note && print.note.color) || '#000000';
doc.fill(doc.format_state.override_color);
} else if (elem === ']]') {
doc.format_state.override_color = null;
doc.fill('black');
} else {
if (doc.format_state.bold && doc.format_state.italic) {
doc.font('ScriptBoldOblique');
} else if (doc.format_state.bold) {
doc.font('ScriptBold');
} else if (doc.format_state.italic) {
doc.font('ScriptOblique');
} else {
doc.font('ScriptNormal');
}
if (elem === '\\_' || elem === '\\*') {
elem = elem.substr(1, 1);
}
inner_text.call(doc, elem, x * 72, y * 72, {
underline: doc.format_state.underline,
lineBreak: options.line_break
});
x += font_width * elem.length;
}
}
};
return doc;
}
function clearFormatting(text) {
var clean = text.replace(/\*/g, '');
clean = clean.replace(/_/g, '');
return clean;
}
function inline(text) {
return text.replace(/\n/g, ' ');
}
function finishDoc(doc, callback, filepath) {
var stream = doc.pipe(create_simplestream(filepath));
doc.end();
stream.on('finish', callback);
}
var get_title_page_token = function(parsed, type) {
var result = null;
if (parsed && parsed.title_page) {
parsed.title_page.forEach(function(token) {
if (token.is(type)) {
result = token;
}
});
}
return result;
};
function generate(doc, opts) {
var parsed = opts.parsed,
cfg = opts.config,
print = opts.print,
lines = parsed.lines;
var title_token = get_title_page_token(parsed, 'title');
var author_token = get_title_page_token(parsed, 'author');
if (!author_token) {
author_token = get_title_page_token(parsed, 'authors');
}
doc.info.Title = title_token ? clearFormatting(inline(title_token.text)) : '';
doc.info.Author = author_token ? clearFormatting(inline(author_token.text)) : '';
doc.info.Creator = 'afterwriting.com';
// helper
var center = function(txt, y) {
var txt_length = txt.replace(/\*/g, '').replace(/_/g, '').length;
var feed = (print.page_width - txt_length * print.font_width) / 2;
doc.text(txt, feed, y);
};
var title_y = print.title_page.top_start;
var title_page_next_line = function() {
title_y += print.line_spacing * print.font_height;
};
var title_page_main = function(parsed, type, options) {
options = options || {};
if (arguments.length === 0) {
title_page_next_line();
return;
}
var token = get_title_page_token(parsed, type);
if (token) {
token.text.split('\n').forEach(function(line) {
if (options.capitalize) {
line = line.toUpperCase();
}
center(line, title_y);
title_page_next_line();
});
}
};
if (cfg.print_title_page) {
// title page
title_page_main(parsed, 'title', {
capitalize: true
});
title_page_main();
title_page_main();
title_page_main(parsed, 'credit');
title_page_main();
title_page_main(parsed, 'author');
title_page_main();
title_page_main();
title_page_main();
title_page_main();
title_page_main(parsed, 'source');
var concat_types = function(parsed, prev, type) {
var token = get_title_page_token(parsed, type);
if (token) {
prev = prev.concat(token.text.split('\n'));
}
return prev;
};
var left_side = print.title_page.left_side.reduce(concat_types.bind(null, parsed), []),
right_side = print.title_page.right_side.reduce(concat_types.bind(null, parsed), []),
title_page_extra = function(x) {
return function(line) {
doc.text(line.trim(), x, title_y);
title_page_next_line();
};
};
title_y = 8.5;
left_side.forEach(title_page_extra(1.3));
title_y = 8.5;
right_side.forEach(title_page_extra(5));
// script
doc.addPage();
}
if (opts.hooks && opts.hooks.before_script) {
opts.hooks.before_script(doc);
}
var y = 0,
page = 1,
scene_number,
prev_scene_continuation_header = '',
scene_continuations = {},
current_section_level = 0,
current_section_number,
current_section_token,
section_number = helper.version_generator(),
text,
after_section = false; // helpful to determine synopsis indentation
var print_header_and_footer = function(continuation_header) {
if (cfg.print_header) {
continuation_header = continuation_header || '';
var offset = helper.blank_text(continuation_header);
if (helper.get_indentation(cfg.print_header).length >= continuation_header.length) {
offset = '';
}
if (offset) {
offset += ' ';
}
doc.format_text(offset + cfg.print_header, 1.5, print.page_number_top_margin, {
color: '#777777'
});
}
if (cfg.print_footer) {
doc.format_text(cfg.print_footer, 1.5, print.page_height - 0.5, {
color: '#777777'
});
}
};
var print_watermark = function() {
if (cfg.print_watermark) {
var options = {
origin: [0, 0]
},
font_size,
angle = Math.atan(print.page_height / print.page_width) * 180 / Math.PI,
diagonal,
watermark, len;
// underline and rotate pdfkit bug (?) workaround
watermark = cfg.print_watermark.replace(/_/g, '');
// unformat
len = watermark.replace(/\*/g, '').length;
diagonal = Math.sqrt(Math.pow(print.page_width, 2) + Math.pow(print.page_height, 2));
diagonal -= 4;
font_size = (1.667 * diagonal) / len * 72;
doc.fontSize(font_size);
doc.rotate(angle, options);
doc.format_text(watermark, 2, -(font_size / 2) / 72, {
color: '#eeeeee',
line_break: false
});
doc.rotate(-angle, options);
doc.fontSize(print.font_size || 12);
}
};
print_watermark();
print_header_and_footer();
lines.forEach(function(line) {
if (line.type === "page_break") {
if (cfg.scene_continuation_bottom && line.scene_split) {
var scene_continued_text = '(' + (cfg.text_scene_continued || 'CONTINUED') + ')';
var feed = print.action.feed + print.action.max * print.font_width - scene_continued_text.length * print.font_width;
doc.simple_text(scene_continued_text, feed * 72, (print.top_margin + print.font_height * (y + 2)) * 72);
}
y = 0;
doc.addPage();
page++;
var number_y = print.page_number_top_margin;
if (cfg.scene_continuation_top && line.scene_split) {
scene_continuations[scene_number] = scene_continuations[scene_number] || 0;
scene_continuations[scene_number]++;
var scene_continued = (cfg.scenes_numbers !== 'none' && scene_number ? scene_number + ' ' : '') + (cfg.text_scene_continued || 'CONTINUED') + ':';
scene_continued += scene_continuations[scene_number] > 1 ? ' (' + scene_continuations[scene_number] + ')' : '';
scene_continued = scene_continued.replace(/\*/g, '');
scene_continued = scene_continued.replace(/_/g, '');
doc.simple_text(scene_continued, print.action.feed * 72, number_y * 72);
prev_scene_continuation_header = scene_continued;
}
if (cfg.show_page_numbers) {
var page_num = page.toFixed() + ".";
var number_x = print.action.feed + print.action.max * print.font_width - page_num.length * print.font_width;
doc.simple_text(page_num, number_x * 72, number_y * 72);
}
print_watermark();
print_header_and_footer(prev_scene_continuation_header);
prev_scene_continuation_header = '';
} else if (line.type === "separator") {
y++;
} else {
// formatting not supported yet
text = line.text;
var color = (print[line.type] && print[line.type].color) || '#000000';
var text_properties = {
color: color
};
if (line.type === 'centered') {
center(text, print.top_margin + print.font_height * y++);
} else {
var feed = (print[line.type] || {}).feed || print.action.feed;
if (line.type === "transition") {
feed = print.action.feed + print.action.max * print.font_width - line.text.length * print.font_width;
}
if (line.type === "scene_heading" && cfg.embolden_scene_headers) {
text = '**' + text + '**';
}
if (line.type === "scene_heading" && cfg.underline_scene_headers) {
text = '_' + text + '_';
}
if (line.type === 'section') {
current_section_level = line.token.level;
feed += current_section_level * print.section.level_indent;
if (cfg.number_sections) {
if (line.token !== current_section_token) {
current_section_number = section_number(line.token.level);
current_section_token = line.token;
text = current_section_number + '. ' + text;
} else {
text = Array(current_section_number.length + 3).join(' ') + text;
}
}
} else if (line.type === 'synopsis') {
feed += print.synopsis.padding || 0;
if (print.synopsis.feed_with_last_section && after_section) {
feed += current_section_level * print.section.level_indent;
} else {
feed = print.action.feed;
}
}
if (print[line.type] && print[line.type].italic && text) {
text = '*' + text + '*';
}
if (line.token && line.token.dual) {
if (line.right_column) {
var y_right = y;
line.right_column.forEach(function(line) {
var feed_right = (print[line.type] || {}).feed || print.action.feed;
feed_right -= (feed_right - print.left_margin) / 2;
feed_right += (print.page_width - print.right_margin - print.left_margin) / 2;
doc.text(line.text, feed_right, print.top_margin + print.font_height * y_right++, text_properties);
});
}
feed -= (feed - print.left_margin) / 2;
}
doc.text(text, feed, print.top_margin + print.font_height * y, text_properties);
if (line.number) {
scene_number = String(line.number);
var scene_text_length = scene_number.length;
if (cfg.embolden_scene_headers) {
scene_number = '**' + scene_number + '**';
}
if (cfg.underline_scene_headers) {
scene_number = '_' + scene_number + '_';
}
var shift_scene_number;
if (cfg.scenes_numbers === 'both' || cfg.scenes_numbers === 'left') {
shift_scene_number = (scene_text_length + 4) * print.font_width;
doc.text(scene_number, feed - shift_scene_number, print.top_margin + print.font_height * y, text_properties);
}
if (cfg.scenes_numbers === 'both' || cfg.scenes_numbers === 'right') {
shift_scene_number = (print.scene_heading.max + 1) * print.font_width;
doc.text(scene_number, feed + shift_scene_number, print.top_margin + print.font_height * y, text_properties);
}
}
y++;
}
}
// clear after section
if (line.type === 'section') {
after_section = true;
} else if (line.type !== 'separator' && line.type !== 'synopsis' && line.type !== 'page_break') {
after_section = false;
}
});
}
module.get_pdf = function(opts) {
var doc = initDoc(opts);
generate(doc, opts);
finishDoc(doc, opts.callback, opts.filepath);
};
return module;
});