pdfkit works much better

This commit is contained in:
Christian Genco 2015-04-06 13:06:26 -05:00
parent 6ca0188d60
commit 22bb6b4f6d
20 changed files with 399 additions and 2 deletions

BIN
.DS_Store vendored

Binary file not shown.

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules

BIN
fonts/Alegreya-Bold.ttf Executable file

Binary file not shown.

BIN
fonts/AlegreyaSans-Light.ttf Executable file

Binary file not shown.

BIN
fonts/Chalkboard.ttc Executable file

Binary file not shown.

BIN
fonts/CrimsonText-Bold.ttf Executable file

Binary file not shown.

BIN
fonts/CrimsonText-BoldItalic.ttf Executable file

Binary file not shown.

BIN
fonts/CrimsonText-Italic.ttf Executable file

Binary file not shown.

BIN
fonts/CrimsonText-Roman.ttf Executable file

Binary file not shown.

BIN
fonts/CrimsonText-Semibold.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
fonts/GoodDog.ttf Executable file

Binary file not shown.

BIN
fonts/Merriweather-Regular.ttf Executable file

Binary file not shown.

BIN
fonts/SourceCodePro-Bold.ttf Executable file

Binary file not shown.

BIN
fonts/SourceCodePro-Regular.ttf Executable file

Binary file not shown.

327
generate.coffee Normal file
View File

@ -0,0 +1,327 @@
fs = require 'fs'
_ = require 'underscore'
vm = require 'vm'
md = require('markdown').markdown
coffee = require 'coffee-script'
CodeMirror = require 'codemirror/addon/runmode/runmode.node'
PDFDocument = require 'pdfkit'
Array::first ?= -> @[0]
Array::last ?= -> @[@length - 1]
process.chdir(__dirname)
# setup code mirror coffeescript mode
filename = require.resolve('codemirror/mode/coffeescript/coffeescript')
coffeeMode = fs.readFileSync filename, 'utf8'
vm.runInNewContext coffeeMode, CodeMirror: CodeMirror
# style definitions for markdown
styles =
# h1:
# font: 'Times-Roman'
# fontSize: 25
# padding: 15
# h2:
# font: 'Times-Roman'
# fontSize: 18
# padding: 10
# h3:
# font: 'Times-Roman'
# fontSize: 18
# padding: 10
meta:
font: 'Times-Roman'
fontSize: 12
lineGap: 24
align: 'left'
indent: 0
title:
font: 'Times-Roman'
fontSize: 12
lineGap: 24
align: 'center'
para:
font: 'Times-Roman'
fontSize: 12
lineGap: 24
align: 'left'
indent: 72
# padding: 10
# code:
# font: 'Times-Roman'
# fontSize: 9
# code_block:
# padding: 10
# background: '#2c2c2c'
# inlinecode:
# font: 'Times-Roman'
# fontSize: 10
# listitem:
# font: 'Times-Roman'
# fontSize: 10
# padding: 6
# link:
# font: 'Times-Roman'
# fontSize: 10
# color: 'blue'
# underline: true
# example:
# font: 'Times-Roman'
# fontSize: 9
# color: 'black'
# padding: 10
em:
font: 'Times-Italic'
align: 'left'
strong:
font: 'Times-Bold'
align: 'left'
# u:
# underline: true
# syntax highlighting colors
# based on Github's theme
colors =
keyword: '#cb4b16'
atom: '#d33682'
number: '#009999'
def: '#2aa198'
variable: '#108888'
'variable-2': '#b58900'
'variable-3': '#6c71c4'
property: '#2aa198'
operator: '#6c71c4'
comment: '#999988'
string: '#dd1144'
'string-2': '#009926'
meta: '#768E04'
qualifier: '#b58900'
builtin: '#d33682'
bracket: '#cb4b16'
tag: '#93a1a1'
attribute: '#2aa198'
header: '#586e75'
quote: '#93a1a1'
link: '#93a1a1'
special: '#6c71c4'
default: '#002b36'
codeBlocks = []
lastType = null
# This class represents a node in the markdown tree, and can render it to pdf
class Node
constructor: (tree) ->
# special case for text nodes
if typeof tree is 'string'
@type = 'text'
@text = tree
return
@type = tree.shift()
@attrs = {}
if typeof tree[0] is 'object' and not Array.isArray tree[0]
@attrs = tree.shift()
# parse sub nodes
@content = while tree.length
new Node tree.shift()
switch @type
when 'header'
@type = 'h' + @attrs.level
when 'code_block'
# use code mirror to syntax highlight the code block
code = @content[0].text
@content = []
CodeMirror.runMode code, 'coffeescript', (text, style) =>
color = colors[style] or colors.default
opts =
color: color
continued: text isnt '\n'
@content.push new Node ['code', opts, text]
@content[@content.length - 1]?.attrs.continued = false
codeBlocks.push code
when 'img'
# images are used to generate inline example output
# compiles the coffeescript to JS so it can be run
# in the render method
@type = 'example'
code = codeBlocks[@attrs.alt]
@code = coffee.compile code if code
@height = +@attrs.title or 0
@style = styles[@type] or styles.para
# sets the styles on the document for this node
setStyle: (doc) ->
if @style.font
doc.font @style.font
if @style.fontSize
doc.fontSize @style.fontSize
if @style.color or @attrs.color
doc.fillColor @style.color or @attrs.color
else
doc.fillColor 'black'
options = {}
options.lineGap = @style.lineGap || 24
options.align = @style.align
options.indent = @style.indent
# options.paragraphGap = 24
options.link = @attrs.href or false # override continued link
options.continued = @attrs.continued if @attrs.continued?
return options
# renders this node and its subnodes to the document
render: (doc, continued = false) ->
switch @type
when 'example'
@setStyle doc
# translate all points in the example code to
# the current point in the document
doc.moveDown()
doc.save()
doc.translate(doc.x, doc.y)
x = doc.x
y = doc.y
doc.x = doc.y = 0
# run the example code with the document
vm.runInNewContext @code,
doc: doc
lorem: lorem
# restore points and styles
y += doc.y
doc.restore()
doc.x = x
doc.y = y + @height
when 'hr'
doc.addPage()
else
# loop through subnodes and render them
for fragment, index in @content
if fragment.type is 'text'
# add a new page for each heading, unless it follows another heading
if @type in ['h1', 'h2'] and lastType? and lastType isnt 'h1'
doc.addPage()
# set styles and whether this fragment is continued (for rich text wrapping)
options = @setStyle doc
options.continued ?= continued or index < @content.length - 1
# remove newlines unless this is code
unless @type is 'code'
fragment.text = fragment.text.replace(/[\r\n]\s*/g, ' ')
doc.text fragment.text, options
else
fragment.render doc, index < @content.length - 1 and @type isnt 'bulletlist'
lastType = @type
if @style.padding
doc.y += @style.padding
# reads and renders a markdown/literate coffeescript file to the document
render = (doc, filename) ->
doc.font 'Times-Roman'
doc.fontSize 12
codeBlocks = []
content = fs.readFileSync(filename, 'utf8')
body = ""
metadata_pattern = /// ^
([\w.-]+) # key
\:\ # colon
\s* # optional whitespace
(.+) # value
$///
metadata = {}
for line in content.split("\n")
if meta = line.match(metadata_pattern)
key = meta[1]
value = meta[2]
metadata[key] = value
else
body += line
metadata.lastName ||= metadata.author?.split(" ").last()
# console.log metadata
# add header
doc.text(metadata.author, styles.meta)
doc.text(metadata.instructor, styles.meta)
doc.text(metadata.course, styles.meta)
doc.text(metadata.date, styles.meta)
doc.text(metadata.title, styles.title)
tree = md.parse body
tree.shift()
while tree.length
node = new Node tree.shift()
node.render(doc)
# add page numbers
range = doc.bufferedPageRange() # => { start: 0, count: 2 }
for i in [range.start...range.start + range.count]
doc.switchToPage(i)
doc.y = 72/2
doc.x = 72
doc.text "#{metadata.lastName} #{i + 1}",
align: 'right'
doc.flushPages()
doc
# renders the title page of the guide
renderTitlePage = (doc) ->
title = 'PDFKit Guide'
author = 'By Devon Govett'
version = 'Version ' + require('./package.json').version
doc.font 'fonts/AlegreyaSans-Light.ttf', 60
doc.y = doc.page.height / 2 - doc.currentLineHeight()
doc.text title, align: 'center'
w = doc.widthOfString(title)
doc.fontSize 20
doc.y -= 10
doc.text author,
align: 'center'
indent: w - doc.widthOfString(author)
doc.font styles.para.font, 10
doc.text version,
align: 'center'
indent: w - doc.widthOfString(version)
doc.addPage()
renderHeader = (doc) ->
do ->
doc = new PDFDocument
bufferPages: true
doc.pipe fs.createWriteStream('guide.pdf')
render doc, 'test.coffee.md'
doc.end()

BIN
guide.pdf Normal file

Binary file not shown.

54
package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "markdowntomla",
"description": "A .md -> MLA-formatted .pdf converter",
"keywords": [
"pdf",
"markdown",
"MLA",
"md",
"document"
],
"version": "0.0.1",
"homepage": "",
"author": {
"name": "Christian Genco",
"email": "christian@gen.co",
"url": "http://christian.gen.co"
},
"repository": {
"type": "git",
"url": "https://github.com/christiangenco/markdowntomla.git"
},
"bugs": "http://github.com/christiangenco/markdowntomla/issues",
"devDependencies": {
"coffee-script": ">=1.0.1",
"codemirror": "~3.20.0",
"markdown": "~0.5.0",
"jade": "~1.1.5",
"coffeeify": "^0.6.0",
"browserify": "^3.39.0",
"brfs": "~1.0.1",
"exorcist": "^0.1.5",
"brace": "^0.2.1",
"blob-stream": "^0.1.2"
},
"dependencies": {
"pdfkit": "~0.7.1",
"underscore": "*",
"png-js": ">=0.1.0",
"linebreak": "~0.1.0"
},
"scripts": {
"prepublish": "make js",
"postpublish": "make clean"
},
"main": "js/document",
"browserify": {
"transform": [
"brfs"
]
},
"engine": [
"node >= v0.10.0"
]
}

13
test.coffee.md Normal file
View File

@ -0,0 +1,13 @@
author: Elizabeth L. Genco
instructor: Professor Patricia Sullivan
course: English 624
date: 12 February 2012
title: Toward a Recovery of Nineteenth Century Farming Handbooks
While researching texts like *Harry Potter* written about nineteenth century farming, I found a few authors who published books about the literature of nineteenth century farming, particularly agricultural journals, newspapers, pamphlets, and brochures. These authors often placed the farming literature they were studying into an historical context by discussing the important events in agriculture of the year in which the literature was published (see Demaree, for example). However, while these authors discuss journals, newspapers, pamphlets, and brochures, I could not find much discussion about another important source of farming knowledge: farming handbooks. My goal in this paper is to bring this source into the agricultural literature discussion by connecting three agricultural handbooks from the nineteenth century with nineteenth century agricultural history.
To achieve this goal, I have organized my paper into four main sections, two of which have sub-sections. In the first section, I provide an account of three important events in nineteenth century agricultural history: population and technological changes, the distribution of scientific new knowledge, and farming's influence on education. In the second section, I discuss three nineteenth century farming handbooks in connection with the important events described in the first section. I end my paper with a third section that offers research questions that could be answered in future versions of this paper and conclude with a fourth section that discusses the importance of expanding this particular project. I also include an appendix after the Works Cited that contains images of the three handbooks I examined. Before I can begin the examination of the three handbooks, however, I need to provide an historical context in which the books were written, and it is to this that I now turn.
By the 1860s, the need for this knowledge was strong enough to affect education. John Nicholson anticipated this effect in 1820 in the “Experiments” section of his book *The Farmers Assistant; Being a Digest of All That Relates to Agriculture and the Conducting of Rural Affairs; Alphabetically Arranged and Adapted for the United States*:
> Perhaps it would be well, if some institution were devised, and supported at the expense of the State, which would be so organized as would tend most effectually to produce a due degree of emulation among Farmers, by rewards and honorary distinctions conferred by those who, by their successful experimental efforts and improvements, should render themselves duly entitled to them. (92)

View File

@ -1,6 +1,7 @@
# Todo
* italic
* new paragraphs
* blockquotes
* headings
* works cited page
* citations
* citations?