### 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