'use strict'; /*! * Canvas * Copyright (c) 2010 LearnBoost * MIT Licensed */ /** * Module dependencies. */ var canvas = require('./bindings') , Canvas = canvas.Canvas , Image = canvas.Image , cairoVersion = canvas.cairoVersion , Context2d = require('./context2d') , PNGStream = require('./pngstream') , PDFStream = require('./pdfstream') , JPEGStream = require('./jpegstream') , FontFace = canvas.FontFace , fs = require('fs') , packageJson = require("../package.json") , FORMATS = ['image/png', 'image/jpeg']; /** * Export `Canvas` as the module. */ var Canvas = exports = module.exports = Canvas; /** * Library version. */ exports.version = packageJson.version; /** * Cairo version. */ exports.cairoVersion = cairoVersion; /** * jpeglib version. */ if (canvas.jpegVersion) { exports.jpegVersion = canvas.jpegVersion; } /** * gif_lib version. */ if (canvas.gifVersion) { exports.gifVersion = canvas.gifVersion.replace(/[^.\d]/g, ''); } /** * freetype version. */ if (canvas.freetypeVersion) { exports.freetypeVersion = canvas.freetypeVersion; } /** * Expose constructors. */ exports.Context2d = Context2d; exports.PNGStream = PNGStream; exports.PDFStream = PDFStream; exports.JPEGStream = JPEGStream; exports.Image = Image; exports.ImageData = canvas.ImageData; if (FontFace) { var Font = function Font(name, path, idx) { this.name = name; this._faces = {}; this.addFace(path, 'normal', 'normal', idx); }; Font.prototype.addFace = function(path, weight, style, idx) { style = style || 'normal'; weight = weight || 'normal'; var face = new FontFace(path, idx || 0); this._faces[weight + '-' + style] = face; }; Font.prototype.getFace = function(weightStyle) { return this._faces[weightStyle] || this._faces['normal-normal']; }; exports.Font = Font; } /** * Context2d implementation. */ require('./context2d'); /** * Image implementation. */ require('./image'); /** * Inspect canvas. * * @return {String} * @api public */ Canvas.prototype.inspect = function(){ return '[Canvas ' + this.width + 'x' + this.height + ']'; }; /** * Get a context object. * * @param {String} contextId * @return {Context2d} * @api public */ Canvas.prototype.getContext = function(contextId){ if ('2d' == contextId) { var ctx = this._context2d || (this._context2d = new Context2d(this)); this.context = ctx; ctx.canvas = this; return ctx; } }; /** * Create a `PNGStream` for `this` canvas. * * @return {PNGStream} * @api public */ Canvas.prototype.pngStream = Canvas.prototype.createPNGStream = function(){ return new PNGStream(this); }; /** * Create a synchronous `PNGStream` for `this` canvas. * * @return {PNGStream} * @api public */ Canvas.prototype.syncPNGStream = Canvas.prototype.createSyncPNGStream = function(){ return new PNGStream(this, true); }; /** * Create a `PDFStream` for `this` canvas. * * @return {PDFStream} * @api public */ Canvas.prototype.pdfStream = Canvas.prototype.createPDFStream = function(){ return new PDFStream(this); }; /** * Create a synchronous `PDFStream` for `this` canvas. * * @return {PDFStream} * @api public */ Canvas.prototype.syncPDFStream = Canvas.prototype.createSyncPDFStream = function(){ return new PDFStream(this, true); }; /** * Create a `JPEGStream` for `this` canvas. * * @param {Object} options * @return {JPEGStream} * @api public */ Canvas.prototype.jpegStream = Canvas.prototype.createJPEGStream = function(options){ return this.createSyncJPEGStream(options); }; /** * Create a synchronous `JPEGStream` for `this` canvas. * * @param {Object} options * @return {JPEGStream} * @api public */ Canvas.prototype.syncJPEGStream = Canvas.prototype.createSyncJPEGStream = function(options){ options = options || {}; // Don't allow the buffer size to exceed the size of the canvas (#674) var maxBufSize = this.width * this.height * 4; var clampedBufSize = Math.min(options.bufsize || 4096, maxBufSize); return new JPEGStream(this, { bufsize: clampedBufSize , quality: options.quality || 75 , progressive: options.progressive || false }); }; /** * Return a data url. Pass a function for async support (required for "image/jpeg"). * * @param {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png" * @param {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1. * @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg". * @return {String} data URL if synchronous (callback omitted) * @api public */ Canvas.prototype.toDataURL = function(a1, a2, a3){ // valid arg patterns (args -> [type, opts, fn]): // [] -> ['image/png', null, null] // [qual] -> ['image/png', null, null] // [undefined] -> ['image/png', null, null] // ['image/png'] -> ['image/png', null, null] // ['image/png', qual] -> ['image/png', null, null] // [fn] -> ['image/png', null, fn] // [type, fn] -> [type, null, fn] // [undefined, fn] -> ['image/png', null, fn] // ['image/png', qual, fn] -> ['image/png', null, fn] // ['image/jpeg', fn] -> ['image/jpeg', null, fn] // ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn] // ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn] // ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn] var type = 'image/png'; var opts = {}; var fn; if ('function' === typeof a1) { fn = a1; } else { if ('string' === typeof a1 && FORMATS.indexOf(a1.toLowerCase()) !== -1) { type = a1.toLowerCase(); } if ('function' === typeof a2) { fn = a2; } else { if ('object' === typeof a2) { opts = a2; } else if ('number' === typeof a2) { opts = {quality: Math.max(0, Math.min(1, a2)) * 100}; } if ('function' === typeof a3) { fn = a3; } else if (undefined !== a3) { throw new TypeError(typeof a3 + ' is not a function'); } } } if (this.width === 0 || this.height === 0) { // Per spec, if the bitmap has no pixels, return this string: var str = "data:,"; if (fn) { setTimeout(function() { fn(null, str); }); } return str; } if ('image/png' === type) { if (fn) { this.toBuffer(function(err, buf){ if (err) return fn(err); fn(null, 'data:image/png;base64,' + buf.toString('base64')); }); } else { return 'data:image/png;base64,' + this.toBuffer().toString('base64'); } } else if ('image/jpeg' === type) { if (undefined === fn) { throw new Error('Missing required callback function for format "image/jpeg"'); } var stream = this.jpegStream(opts); // note that jpegStream is synchronous var buffers = []; stream.on('data', function (chunk) { buffers.push(chunk); }); stream.on('error', function (err) { fn(err); }); stream.on('end', function() { var result = 'data:image/jpeg;base64,' + Buffer.concat(buffers).toString('base64'); fn(null, result); }); } };