You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/pitfall/pdfkit/lib/document.coffee

237 lines
5.3 KiB
CoffeeScript

###
PDFDocument - represents an entire PDF document
By Devon Govett
###
stream = require 'stream'
fs = require 'fs'
PDFObject = require './object'
PDFReference = require './reference'
PDFPage = require './page'
class PDFDocument extends stream.Readable
constructor: (@options = {}) ->
super
# PDF version
@version = 1.3
# Whether streams should be compressed
@compress = @options.compress ? yes
@_pageBuffer = []
@_pageBufferStart = 0
# The PDF object store
@_offsets = []
@_waiting = 0
@_ended = false
@_offset = 0
@_root = @ref
Type: 'Catalog'
Pages: @ref
Type: 'Pages'
Count: 0
Kids: []
# The current page
@page = null
# Initialize mixins
@initColor()
@initVector()
@initFonts()
@initText()
@initImages()
# Initialize the metadata
@info =
Producer: 'PDFKit'
Creator: 'PDFKit'
CreationDate: new Date()
if @options.info
for key, val of @options.info
@info[key] = val
# Write the header
# PDF version
@_write "%PDF-#{@version}"
# 4 binary chars, as recommended by the spec
@_write "%\xFF\xFF\xFF\xFF"
# Add the first page
if @options.autoFirstPage isnt false
@addPage()
mixin = (methods) =>
for name, method of methods
this::[name] = method
# Load mixins
mixin require './mixins/color'
mixin require './mixins/vector'
mixin require './mixins/fonts'
mixin require './mixins/text'
mixin require './mixins/images'
mixin require './mixins/annotations'
addPage: (options = @options) ->
# end the current page if needed
@flushPages() unless @options.bufferPages
# create a page object
@page = new PDFPage(this, options)
@_pageBuffer.push(@page)
# add the page to the object store
pages = @_root.data.Pages.data
pages.Kids.push @page.dictionary
pages.Count++
# reset x and y coordinates
@x = @page.margins.left
@y = @page.margins.top
# flip PDF coordinate system so that the origin is in
# the top left rather than the bottom left
@_ctm = [1, 0, 0, 1, 0, 0]
@transform 1, 0, 0, -1, 0, @page.height
@emit('pageAdded')
return this
bufferedPageRange: ->
return { start: @_pageBufferStart, count: @_pageBuffer.length }
switchToPage: (n) ->
unless page = @_pageBuffer[n - @_pageBufferStart]
throw new Error "switchToPage(#{n}) out of bounds, current buffer covers pages #{@_pageBufferStart} to #{@_pageBufferStart + @_pageBuffer.length - 1}"
@page = page
flushPages: ->
# this local variable exists so we're future-proof against
# reentrant calls to flushPages.
pages = @_pageBuffer
@_pageBuffer = []
@_pageBufferStart += pages.length
for page in pages
page.end()
return
ref: (data) ->
ref = new PDFReference(this, @_offsets.length + 1, data)
@_offsets.push null # placeholder for this object's offset once it is finalized
@_waiting++
return ref
_read: ->
# do nothing, but this method is required by node
_write: (data) ->
unless Buffer.isBuffer(data)
data = new Buffer(data + '\n', 'binary')
@push data
@_offset += data.length
addContent: (data) ->
@page.write data
return this
_refEnd: (ref) ->
#console.log(ref.id)
#console.log(@_offsets)
@_offsets[ref.id - 1] = ref.offset
if --@_waiting is 0 and @_ended
#console.log("finalize") ;
@_finalize()
@_ended = false
write: (filename, fn) ->
# print a deprecation warning with a stacktrace
err = new Error '
PDFDocument#write is deprecated, and will be removed in a future version of PDFKit.
Please pipe the document into a Node stream.
'
console.warn err.stack
@pipe fs.createWriteStream(filename)
@end()
@once 'end', fn
output: (fn) ->
# more difficult to support this. It would involve concatenating all the buffers together
throw new Error '
PDFDocument#output is deprecated, and has been removed from PDFKit.
Please pipe the document into a Node stream.
'
end: ->
#console.log("start document end")
#console.log(@_offsets)
@flushPages()
@_info = @ref()
for key, val of @info
if typeof val is 'string'
val = new String val
@_info.data[key] = val
#console.log(@_offsets)
@_info.end()
for name, font of @_fontFamilies
font.finalize()
#console.log(@_offsets)
@_root.end()
#console.log(@_offsets)
@_root.data.Pages.end()
#console.log(@_offsets)
if @_waiting is 0
#console.log(@_offsets.length)
#console.log("finalize2") ;
@_finalize()
else
#console.log("ended is true") ;
@_ended = true
_finalize: (fn) ->
# generate xref
xRefOffset = @_offset
@_write "xref"
@_write "0 #{@_offsets.length + 1}"
@_write "0000000000 65535 f "
for offset in @_offsets
offset = ('0000000000' + offset).slice(-10)
@_write offset + ' 00000 n '
# trailer
@_write 'trailer'
@_write PDFObject.convert
Size: @_offsets.length + 1
Root: @_root
Info: @_info
@_write 'startxref'
@_write "#{xRefOffset}"
@_write '%%EOF'
# end the stream
@push null
toString: ->
"[object PDFDocument]"
module.exports = PDFDocument