### 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 = yes # The PDF object store @_objects = [] @_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 @addPage() mixin = (name) => methods = require './mixins/' + name for name, method of methods this::[name] = method # Load mixins mixin 'color' mixin 'vector' mixin 'fonts' mixin 'text' mixin 'images' mixin 'annotations' addPage: (options = @options) -> # end the current page if needed @page?.end() # create a page object @page = new PDFPage(this, options) # 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 return this ref: (data) -> ref = new PDFReference(this, @_objects.length + 1, data) @_objects.push ref @_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: -> if --@_waiting is 0 and @_ended @_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: -> @page.end() @_info = @ref() for key, val of @info if typeof val is 'string' val = PDFObject.s val, true @_info.data[key] = val @_info.end() for name, font of @_fontFamilies font.embed() @_root.end() @_root.data.Pages.end() if @_waiting is 0 @_finalize() else @_ended = true _finalize: (fn) -> # generate xref xRefOffset = @_offset @_write "xref" @_write "0 #{@_objects.length + 1}" @_write "0000000000 65535 f " for ref in @_objects offset = ('0000000000' + ref.offset).slice(-10) @_write offset + ' 00000 n ' # trailer @_write 'trailer' @_write PDFObject.convert Size: @_objects.length Root: @_root Info: @_info @_write 'startxref' @_write "#{xRefOffset}" @_write '%%EOF' # end the stream @push null toString: -> "[object PDFDocument]" module.exports = PDFDocument