You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
7.0 KiB
312 lines
7.0 KiB
'use strict';
|
|
|
|
/*!
|
|
* Canvas
|
|
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|
* 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]
|
|
|
|
if (this.width === 0 || this.height === 0) {
|
|
// Per spec, if the bitmap has no pixels, return this string:
|
|
return "data:,";
|
|
}
|
|
|
|
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 ('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);
|
|
});
|
|
}
|
|
};
|
|
|