diff --git a/.gitignore b/.gitignore index c5175f72b..4530372e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ lib/font/tables/.DS_Store node-zlib/ src/ playground/ -*.html \ No newline at end of file +build/ +js/ +demo/bundle.js +*.html +!demo/browser.html \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..044f423a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +js: lib/**/*.coffee + ./node_modules/.bin/coffee -o js -c lib/ + cp -r lib/font/data js/font/data + +browser: lib/**/*.coffee + mkdir -p build/ + ./node_modules/.bin/browserify \ + --standalone PDFDocument \ + --debug \ + --transform coffeeify \ + --extension .coffee \ + lib/document.coffee \ + | ./node_modules/.bin/exorcist build/pdfkit.js.map > build/pdfkit.js + +browser-demo: js demo/browser.js + ./node_modules/.bin/browserify demo/browser.js > demo/bundle.js + +docs: pdf-guide website browser-demo + +pdf-guide: + ./node_modules/.bin/coffee docs/generate.coffee + +website: + mkdir -p docs/img + ./node_modules/.bin/coffee docs/generate_website.coffee + +clean: + rm -rf js build demo/bundle.js diff --git a/README.md b/README.md index 49a169e99..4d70f616a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # PDFKit -### A PDF generation library for Node.js. + +A JavaScript PDF generation library for Node and the browser. ## Description -PDFKit is a PDF document generation library for Node that makes creating complex, multi-page, printable documents easy. +PDFKit is a PDF document generation library for Node and the browser that makes creating complex, multi-page, printable documents easy. It's written in CoffeeScript, but you can choose to use the API in plain 'ol JavaScript if you like. The API embraces chainability, and includes both low level functions as well as abstractions for higher level functionality. The PDFKit API is designed to be simple, so generating complex documents is often as simple as a few function calls. @@ -54,52 +55,101 @@ Installation uses the [npm](http://npmjs.org/) package manager. Just type the f ## Example - PDFDocument = require 'pdfkit' - - # Create a document - doc = new PDFDocument - - # Pipe it's output somewhere, like to a file or HTTP response - doc.pipe fs.createWriteStream('output.pdf') - - # Embed a font, set the font size, and render some text - doc.font('fonts/PalatinoBold.ttf') - .fontSize(25) - .text('Some text with an embedded font!', 100, 100) - - # Add another page - doc.addPage() - .fontSize(25) - .text('Here is some vector graphics...', 100, 100) - - # Draw a triangle - doc.save() - .moveTo(100, 150) - .lineTo(100, 250) - .lineTo(200, 250) - .fill("#FF3300") - - # Apply some transforms and render an SVG path with the 'even-odd' fill rule - doc.scale(0.6) - .translate(470, -380) - .path('M 250,75 L 323,301 131,161 369,161 177,301 z') - .fill('red', 'even-odd') - .restore() - - # Add some text with annotations - doc.addPage() - .fillColor("blue") - .text('Here is a link!', 100, 100) - .underline(100, 100, 160, 27, color: "#0000FF") - .link(100, 100, 160, 27, 'http://google.com/') - - # Finalize PDF file - doc.end() - +```coffeescript +PDFDocument = require 'pdfkit' + +# Create a document +doc = new PDFDocument + +# Pipe it's output somewhere, like to a file or HTTP response +# See below for browser usage +doc.pipe fs.createWriteStream('output.pdf') + +# Embed a font, set the font size, and render some text +doc.font('fonts/PalatinoBold.ttf') + .fontSize(25) + .text('Some text with an embedded font!', 100, 100) + +# Add another page +doc.addPage() + .fontSize(25) + .text('Here is some vector graphics...', 100, 100) + +# Draw a triangle +doc.save() + .moveTo(100, 150) + .lineTo(100, 250) + .lineTo(200, 250) + .fill("#FF3300") + +# Apply some transforms and render an SVG path with the 'even-odd' fill rule +doc.scale(0.6) + .translate(470, -380) + .path('M 250,75 L 323,301 131,161 369,161 177,301 z') + .fill('red', 'even-odd') + .restore() + +# Add some text with annotations +doc.addPage() + .fillColor("blue") + .text('Here is a link!', 100, 100) + .underline(100, 100, 160, 27, color: "#0000FF") + .link(100, 100, 160, 27, 'http://google.com/') + +# Finalize PDF file +doc.end() +``` + [The PDF output from this example](http://pdfkit.org/demo/out.pdf) (with a few additions) shows the power of PDFKit — producing complex documents with a very small amount of code. For more, see the `demo` folder and the [PDFKit programming guide](http://pdfkit.org/docs/getting_started.html). +## Browser Usage + +There are two ways to use PDFKit in the browser. The first is to use [Browserify](http://browserify.org/), +which is a Node module packager for the browser with the familiar `require` syntax. The second is to use +a prebuilt version of PDFKit, which you can [download from Github](https://github.com/devongovett/pdfkit/releases). + +In addition to PDFKit, you'll need somewhere to stream the output to. HTML5 has a +[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object which can be used to store binary data, and +get URLs to this data in order to display PDF output inside an iframe, or upload to a server, etc. In order to +get a Blob from the output of PDFKit, you can use the [blob-stream](https://github.com/devongovett/blob-stream) +module. + +The following example uses Browserify to load `PDFKit` and `blob-stream`, but if you're not using Browserify, +you can load them in whatever way you'd like (e.g. script tags). + +```coffeescript +# require dependencies +PDFDocument = require 'pdfkit' +blobStream = require 'blob-stream' + +# create a document the same way as above +doc = new PDFDocument + +# pipe the document to a blob +stream = doc.pipe(blobStream()) + +# add your content to the document here, as usual + +# get a blob when you're done +doc.end() +stream.on 'finish', -> + # get a blob you can do whatever you like with + blob = stream.toBlob('application/pdf') + + # or get a blob URL for display in the browser + url = stream.toBlobURL('application/pdf') + iframe.src = url +``` + +You can see an interactive in-browser demo of PDFKit [here](http://pdfkit.org/demo/browser.html). + +Note that in order to Browserify a project using PDFKit, you need to install the `brfs` module with npm, +which is used to load built-in font data into the package. It is listed as a `devDependency` in +PDFKit's `package.json`, so it isn't installed by default for Node users. +If you forget to install it, Browserify will print an error message. + ## Documentation For complete API documentation and more examples, see the [PDFKit website](http://pdfkit.org/). diff --git a/demo/browser.html b/demo/browser.html new file mode 100644 index 000000000..285f933cc --- /dev/null +++ b/demo/browser.html @@ -0,0 +1,39 @@ + + + + + + + +

PDFKit Browser Demo

+

Website | Github

+
+ + + + \ No newline at end of file diff --git a/demo/browser.js b/demo/browser.js new file mode 100644 index 000000000..7c78ae0eb --- /dev/null +++ b/demo/browser.js @@ -0,0 +1,76 @@ +var PDFDocument = require('../'); +var blobStream = require('blob-stream'); +var ace = require('brace'); +require('brace/mode/javascript'); +require('brace/theme/monokai'); + +var lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.'; + +function makePDF(PDFDocument, blobStream, lorem, iframe) { + // create a document and pipe to a blob + var doc = new PDFDocument(); + var stream = doc.pipe(blobStream()); + + // draw some text + doc.fontSize(25) + .text('Here is some vector graphics...', 100, 80); + + // some vector graphics + doc.save() + .moveTo(100, 150) + .lineTo(100, 250) + .lineTo(200, 250) + .fill("#FF3300"); + + doc.circle(280, 200, 50) + .fill("#6600FF"); + + // an SVG path + doc.scale(0.6) + .translate(470, 130) + .path('M 250,75 L 323,301 131,161 369,161 177,301 z') + .fill('red', 'even-odd') + .restore(); + + // and some justified text wrapped into columns + doc.text('And here is some wrapped text...', 100, 300) + .font('Times-Roman', 13) + .moveDown() + .text(lorem, { + width: 412, + align: 'justify', + indent: 30, + columns: 2, + height: 300, + ellipsis: true + }); + + // end and display the document in the iframe to the right + doc.end(); + stream.on('finish', function() { + iframe.src = stream.toBlobURL('application/pdf'); + }); +} + +var editor = ace.edit('editor'); +editor.setTheme('ace/theme/monokai'); +editor.getSession().setMode('ace/mode/javascript'); +editor.setValue( + makePDF + .toString() + .split('\n').slice(1, -1).join('\n') + .replace(/^ /mg, '') +); +editor.getSession().getSelection().clearSelection(); + +var iframe = document.querySelector('iframe'); +makePDF(PDFDocument, blobStream, lorem, iframe); + +editor.getSession().on('change', function() { + try { + var fn = new Function("PDFDocument", "blobStream", "lorem", "iframe", editor.getValue()); + fn(PDFDocument, blobStream, lorem, iframe); + } catch (e) { + console.log(e) + }; +}); diff --git a/docs/generate.coffee b/docs/generate.coffee index 6287da349..7dae10d86 100644 --- a/docs/generate.coffee +++ b/docs/generate.coffee @@ -5,6 +5,8 @@ coffee = require 'coffee-script' CodeMirror = require 'codemirror/addon/runmode/runmode.node' PDFDocument = require '../' +process.chdir(__dirname) + # setup code mirror coffeescript mode filename = require.resolve('codemirror/mode/coffeescript/coffeescript') coffeeMode = fs.readFileSync filename, 'utf8' diff --git a/docs/generate_website.coffee b/docs/generate_website.coffee index 13a416a6e..89c007c45 100644 --- a/docs/generate_website.coffee +++ b/docs/generate_website.coffee @@ -6,6 +6,8 @@ coffee = require 'coffee-script' {exec} = require 'child_process' PDFDocument = require '../' +process.chdir(__dirname) + files = [ '../README.md' 'getting_started.coffee.md' @@ -79,6 +81,11 @@ generateImages = (tree) -> pages = [] for file in files content = fs.readFileSync file, 'utf8' + + # turn github highlighted code blocks into normal markdown code blocks + content = content.replace /^```coffeescript\n((:?.|\n)*?)\n```/mg, (m, $1) -> + ' ' + $1.split('\n').join('\n ') + tree = markdown.parse(content) headers = extractHeaders(tree) generateImages(tree) diff --git a/docs/getting_started.coffee.md b/docs/getting_started.coffee.md index deced23e4..b67e6082d 100644 --- a/docs/getting_started.coffee.md +++ b/docs/getting_started.coffee.md @@ -26,11 +26,58 @@ the `end` method to finalize it. Here is an example showing how to pipe to a fil # add stuff to PDF here using methods described below... - doc.end() # finalize the PDF and end the stream + # finalize the PDF and end the stream + doc.end() The `write` and `output` methods found in PDFKit before version 0.5 are now deprecated. -### Adding pages +## Using PDFKit in the browser + +As of version 0.6, PDFKit can be used in the browser as well as in Node! +There are two ways to use PDFKit in the browser. The first is to use [Browserify](http://browserify.org/), +which is a Node module packager for the browser with the familiar `require` syntax. The second is to use +a prebuilt version of PDFKit, which you can [download from Github](https://github.com/devongovett/pdfkit/releases). + +Using PDFKit in the browser is exactly the same as using it in Node, except you'll want to pipe the +output to a destination supported in the browser, such as a +[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob). Blobs can be used +to generate a URL to allow display of generated PDFs directly in the browser via an `iframe`, or they can +be used to upload the PDF to a server, or trigger a download in the user's browser. + +To get a Blob from a `PDFDocument`, you should pipe it to a [blob-stream](https://github.com/devongovett/blob-stream), +which is a module that generates a Blob from any Node-style stream. The following example uses Browserify to load +`PDFKit` and `blob-stream`, but if you're not using Browserify, you can load them in whatever way you'd like (e.g. script tags). + + # require dependencies + PDFDocument = require 'pdfkit' + blobStream = require 'blob-stream' + + # create a document the same way as above + doc = new PDFDocument + + # pipe the document to a blob + stream = doc.pipe(blobStream()) + + # add your content to the document here, as usual + + # get a blob when you're done + doc.end() + stream.on 'finish', -> + # get a blob you can do whatever you like with + blob = stream.toBlob('application/pdf') + + # or get a blob URL for display in the browser + url = stream.toBlobURL('application/pdf') + iframe.src = url + +You can see an interactive in-browser demo of PDFKit [here](http://pdfkit.org/demo/browser.html). + +Note that in order to Browserify a project using PDFKit, you need to install the `brfs` module with npm, +which is used to load built-in font data into the package. It is listed as a `devDependency` in +PDFKit's `package.json`, so it isn't installed by default for Node users. +If you forget to install it, Browserify will print an error message. + +## Adding pages The first page of a PDFKit document is added for you automatically when you create the document. Subsequent pages must be added by you. Luckily, it is @@ -65,9 +112,13 @@ For example: # Add different margins on each side doc.addPage - margins: { top: 50, bottom: 50, left: 72, right: 72 } + margins: + top: 50 + bottom: 50 + left: 72 + right: 72 -### Setting document metadata +## Setting document metadata PDF documents can have various metadata associated with them, such as the title, or author of the document. You can add that information by adding it to diff --git a/docs/guide.pdf b/docs/guide.pdf index f6d6e2a55..49abc67c6 100644 Binary files a/docs/guide.pdf and b/docs/guide.pdf differ diff --git a/docs/template.jade b/docs/template.jade index e8ffc003d..d239d99fa 100644 --- a/docs/template.jade +++ b/docs/template.jade @@ -29,6 +29,8 @@ html a(href='/docs/guide.pdf') PDF Guide li a(href='/demo/out.pdf') Example PDF + li + a(href='/demo/browser.html') Interactive Browser Demo li a(href='http://github.com/devongovett/pdfkit') Source Code diff --git a/docs/text.coffee.md b/docs/text.coffee.md index d31f641eb..387860553 100644 --- a/docs/text.coffee.md +++ b/docs/text.coffee.md @@ -144,10 +144,10 @@ and Datafork TrueType (`.dfont`) formats. To change the font used to render text, just call the `font` method. If you are using a standard PDF font, just pass the name to the `font` method. -Otherwise, pass the path to the font file, and if the font is a collection -font (`.ttc` and `.dfont` files), meaning that they contain multiple styles in -the same file, you should pass the name of the style to be extracted from the -collection. +Otherwise, pass the path to the font file, or a `Buffer` containing the font data. +If the font is a collection font (`.ttc` and `.dfont` files), meaning that it +contains multiple styles in the same file, you should pass the name of the style +to be extracted from the collection. Here is an example showing how to set the font in each case. diff --git a/lib/document.coffee b/lib/document.coffee index e2652c055..b86d5e258 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -62,18 +62,17 @@ class PDFDocument extends stream.Readable # Add the first page @addPage() - mixin = (name) => - methods = require './mixins/' + name + mixin = (methods) => for name, method of methods this::[name] = method # Load mixins - mixin 'color' - mixin 'vector' - mixin 'fonts' - mixin 'text' - mixin 'images' - mixin 'annotations' + 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 diff --git a/lib/font.coffee b/lib/font.coffee index 9fbf44b56..99544e78d 100644 --- a/lib/font.coffee +++ b/lib/font.coffee @@ -6,46 +6,78 @@ By Devon Govett TTFFont = require './font/ttf' AFMFont = require './font/afm' Subset = require './font/subset' -zlib = require 'zlib' +fs = require 'fs' class PDFFont - constructor: (@document, @filename, @family, @id) -> - @ref = @document.ref() - - if @filename in @_standardFonts - @isAFM = true - @font = AFMFont.open __dirname + "/font/data/#{@filename}.afm" - @registerAFM() + constructor: (@document, src, family, @id) -> + if typeof src is 'string' + if src of STANDARD_FONTS + @isAFM = true + @font = new AFMFont STANDARD_FONTS[src]() + @registerAFM src - else if /\.(ttf|ttc)$/i.test @filename - @font = TTFFont.open @filename, @family - @subset = new Subset @font - @registerTTF() + else if /\.(ttf|ttc)$/i.test src + @font = TTFFont.open src, family + @subset = new Subset @font + @registerTTF() - else if /\.dfont$/i.test @filename - @font = TTFFont.fromDFont @filename, @family + else if /\.dfont$/i.test src + @font = TTFFont.fromDFont src, family + @subset = new Subset @font + @registerTTF() + + else + throw new Error 'Not a supported font format or standard PDF font.' + + else if Buffer.isBuffer(src) + @font = TTFFont.fromBuffer src, family @subset = new Subset @font @registerTTF() else throw new Error 'Not a supported font format or standard PDF font.' + # This insanity is so browserify can inline the font files + STANDARD_FONTS = + "Courier": -> fs.readFileSync __dirname + "/font/data/Courier.afm", 'utf8' + "Courier-Bold": -> fs.readFileSync __dirname + "/font/data/Courier-Bold.afm", 'utf8' + "Courier-Oblique": -> fs.readFileSync __dirname + "/font/data/Courier-Oblique.afm", 'utf8' + "Courier-BoldOblique": -> fs.readFileSync __dirname + "/font/data/Courier-BoldOblique.afm", 'utf8' + "Helvetica": -> fs.readFileSync __dirname + "/font/data/Helvetica.afm", 'utf8' + "Helvetica-Bold": -> fs.readFileSync __dirname + "/font/data/Helvetica-Bold.afm", 'utf8' + "Helvetica-Oblique": -> fs.readFileSync __dirname + "/font/data/Helvetica-Oblique.afm", 'utf8' + "Helvetica-BoldOblique": -> fs.readFileSync __dirname + "/font/data/Helvetica-BoldOblique.afm", 'utf8' + "Times-Roman": -> fs.readFileSync __dirname + "/font/data/Times-Roman.afm", 'utf8' + "Times-Bold": -> fs.readFileSync __dirname + "/font/data/Times-Bold.afm", 'utf8' + "Times-Italic": -> fs.readFileSync __dirname + "/font/data/Times-Italic.afm", 'utf8' + "Times-BoldItalic": -> fs.readFileSync __dirname + "/font/data/Times-BoldItalic.afm", 'utf8' + "Symbol": -> fs.readFileSync __dirname + "/font/data/Symbol.afm", 'utf8' + "ZapfDingbats": -> fs.readFileSync __dirname + "/font/data/ZapfDingbats.afm", 'utf8' + use: (characters) -> @subset?.use characters embed: -> + return if @embedded or not @dictionary? + if @isAFM @embedAFM() else @embedTTF() + + @embedded = true encode: (text) -> if @isAFM @font.encodeText text else @subset?.encodeText(text) or text + + ref: -> + @dictionary ?= @document.ref() registerTTF: -> + @name = @font.name.postscriptName @scaleFactor = 1000.0 / @font.head.unitsPerEm @bbox = (Math.round e * @scaleFactor for e in @font.bbox) @stemV = 0 # not sure how to compute this for true-type fonts... @@ -80,7 +112,7 @@ class PDFFont throw new Error 'No unicode cmap for font' if not @font.cmap.unicode embedTTF: -> - data = @subset.encode() + data = @subset.encode() fontfile = @document.ref() fontfile.write data @@ -109,7 +141,7 @@ class PDFFont cmap = @document.ref() cmap.end toUnicodeCmap(@subset.subset) - @ref.data = + @dictionary.data = Type: 'Font' BaseFont: @subset.postscriptName Subtype: 'TrueType' @@ -120,7 +152,7 @@ class PDFFont Encoding: 'MacRomanEncoding' ToUnicode: cmap - @ref.end() + @dictionary.end() toUnicodeCmap = (map) -> unicodeMap = ''' @@ -158,35 +190,18 @@ class PDFFont end ''' - registerAFM: -> + registerAFM: (@name) -> {@ascender,@decender,@bbox,@lineGap} = @font embedAFM: -> - @ref.data = + @dictionary.data = Type: 'Font' - BaseFont: @filename + BaseFont: @name Subtype: 'Type1' Encoding: 'WinAnsiEncoding' - @ref.end() + @dictionary.end() - _standardFonts: [ - "Courier" - "Courier-Bold" - "Courier-Oblique" - "Courier-BoldOblique" - "Helvetica" - "Helvetica-Bold" - "Helvetica-Oblique" - "Helvetica-BoldOblique" - "Times-Roman" - "Times-Bold" - "Times-Italic" - "Times-BoldItalic" - "Symbol" - "ZapfDingbats" - ] - widthOfString: (string, size) -> string = '' + string width = 0 diff --git a/lib/font/afm.coffee b/lib/font/afm.coffee index 5d9c0c5a5..a73945103 100644 --- a/lib/font/afm.coffee +++ b/lib/font/afm.coffee @@ -2,11 +2,9 @@ fs = require 'fs' class AFMFont @open: (filename) -> - new AFMFont(filename) + new AFMFont fs.readFileSync filename, 'utf8' - constructor: (filename) -> - @contents = fs.readFileSync filename, 'utf8' - + constructor: (@contents) -> @attributes = {} @glyphWidths = {} @boundingBoxes = {} diff --git a/lib/font/dfont.coffee b/lib/font/dfont.coffee index e2faedd7d..6f733aa96 100644 --- a/lib/font/dfont.coffee +++ b/lib/font/dfont.coffee @@ -87,7 +87,7 @@ class DFont data = @contents pos = data.pos - entry = @map.sfnt.named[name] + entry = @map.sfnt?.named[name] throw new Error "Font #{name} not found in DFont file." unless entry data.pos = entry.offset diff --git a/lib/font/subset.coffee b/lib/font/subset.coffee index 2a6feb0e0..711a47104 100644 --- a/lib/font/subset.coffee +++ b/lib/font/subset.coffee @@ -29,7 +29,7 @@ class Subset return string - cmap: -> + generateCmap: -> # generate the cmap table for this subset unicodeCmap = @font.cmap.tables[0].codeMap mapping = {} @@ -67,7 +67,7 @@ class Subset encode: -> # generate the Cmap for this subset - cmap = CmapTable.encode @cmap(), 'unicode' + cmap = CmapTable.encode @generateCmap(), 'unicode' glyphs = @glyphsFor @glyphIDs() # compute old2new and new2old mapping tables diff --git a/lib/font/ttf.coffee b/lib/font/ttf.coffee index 66e0a38a5..59023b1e7 100644 --- a/lib/font/ttf.coffee +++ b/lib/font/ttf.coffee @@ -22,7 +22,25 @@ class TTFFont @fromDFont: (filename, family) -> dfont = DFont.open(filename) new TTFFont dfont.getNamedFont(family) - + + @fromBuffer: (buffer, family) -> + try + ttf = new TTFFont buffer, family + + # check some tables to make sure this is valid + unless ttf.head.exists and ttf.name.exists and ttf.cmap.exists + # if not, try a DFont + dfont = new DFont buffer + ttf = new TTFFont dfont.getNamedFont(family) + + # check again after dfont + unless ttf.head.exists and ttf.name.exists and ttf.cmap.exists + throw new Error 'Invalid TTF file in DFont' + + return ttf + catch e + throw new Error 'Unknown font format in buffer: ' + e.message + constructor: (@rawData, name) -> data = @contents = new Data(rawData) diff --git a/lib/mixins/fonts.coffee b/lib/mixins/fonts.coffee index b56872fab..60e6bc05e 100644 --- a/lib/mixins/fonts.coffee +++ b/lib/mixins/fonts.coffee @@ -15,25 +15,41 @@ module.exports = # Set the default font @font 'Helvetica' - font: (filename, family, size) -> + font: (src, family, size) -> if typeof family is 'number' size = family family = null - if @_registeredFonts[filename] - {filename, family} = @_registeredFonts[filename] + # check registered fonts if src is a string + if typeof src is 'string' and @_registeredFonts[src] + cacheKey = src + {src, family} = @_registeredFonts[src] + else + cacheKey = family or src + cacheKey = null unless typeof cacheKey is 'string' @fontSize size if size? - family ?= filename - if @_fontFamilies[family] - @_font = @_fontFamilies[family] + # fast path: check if the font is already in the PDF + if font = @_fontFamilies[cacheKey] + @_font = font return this + # load the font id = 'F' + (++@_fontCount) - @_font = new PDFFont(this, filename, family, id) - @_fontFamilies[family] = @_font + @_font = new PDFFont(this, src, family, id) + # check for existing font familes with the same name already in the PDF + # useful if the font was passed as a buffer + if font = @_fontFamilies[@_font.name] + @_font = font + return this + + # save the font for reuse later + if cacheKey + @_fontFamilies[cacheKey] = @_font + + @_fontFamilies[@_font.name] = @_font return this fontSize: (@_fontSize) -> @@ -42,9 +58,9 @@ module.exports = currentLineHeight: (includeGap = false) -> @_font.lineHeight @_fontSize, includeGap - registerFont: (name, path, family) -> + registerFont: (name, src, family) -> @_registeredFonts[name] = - filename: path + src: src family: family return this diff --git a/lib/mixins/text.coffee b/lib/mixins/text.coffee index 5fe29e2f7..a558d993b 100644 --- a/lib/mixins/text.coffee +++ b/lib/mixins/text.coffee @@ -225,7 +225,7 @@ module.exports = y = @page.height - y - (@_font.ascender / 1000 * @_fontSize) # add current font to page if necessary - @page.fonts[@_font.id] ?= @_font.ref + @page.fonts[@_font.id] ?= @_font.ref() # tell the font subset to use the characters @_font.use(text) diff --git a/package.json b/package.json index aef2c1c03..7fcee142b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "document", "vector" ], - "version": "0.5.3", + "version": "0.6.0", "homepage": "http://pdfkit.org/", "author": { "name": "Devon Govett", @@ -25,18 +25,28 @@ "coffee-script": ">=1.0.1", "codemirror": "~3.20.0", "markdown": "~0.5.0", - "jade": "~1.1.5" + "jade": "~1.1.5", + "coffeeify": "^0.6.0", + "browserify": "^3.39.0", + "brfs": "^1.0.1", + "exorcist": "^0.1.5", + "brace": "^0.2.1" }, "dependencies": { "png-js": ">=0.1.0", "linebreak": "~0.1.0" }, "scripts": { - "prepublish": "coffee -o js -c lib/ && cp -r lib/font/data js/font/data", - "postpublish": "rm -rf ./js" + "prepublish": "make js", + "postpublish": "make clean" + }, + "main": "js/document", + "browserify": { + "transform": [ + "brfs" + ] }, - "main": "./js/document", "engine": [ - "node >= v0.6.0" + "node >= v0.10.0" ] }