/*! * Stylus - plugin - url * Copyright (c) Automattic * MIT Licensed */ /** * Module dependencies. */ var utils = require('../utils') , nodes = require('../nodes') , fs = require('fs') , path = require('path') , sax = require('sax'); /** * Initialize a new `Image` with the given `ctx` and `path. * * @param {Evaluator} ctx * @param {String} path * @api private */ var Image = module.exports = function Image(ctx, path) { this.ctx = ctx; this.path = utils.lookup(path, ctx.paths); if (!this.path) throw new Error('failed to locate file ' + path); }; /** * Open the image for reading. * * @api private */ Image.prototype.open = function(){ this.fd = fs.openSync(this.path, 'r'); this.length = fs.fstatSync(this.fd).size; this.extname = path.extname(this.path).slice(1); }; /** * Close the file. * * @api private */ Image.prototype.close = function(){ if (this.fd) fs.closeSync(this.fd); }; /** * Return the type of image, supports: * * - gif * - png * - jpeg * - svg * * @return {String} * @api private */ Image.prototype.type = function(){ var type , buf = new Buffer(4); fs.readSync(this.fd, buf, 0, 4, 0); // GIF if (0x47 == buf[0] && 0x49 == buf[1] && 0x46 == buf[2]) type = 'gif'; // PNG else if (0x50 == buf[1] && 0x4E == buf[2] && 0x47 == buf[3]) type = 'png'; // JPEG else if (0xff == buf[0] && 0xd8 == buf[1]) type = 'jpeg'; // SVG else if ('svg' == this.extname) type = this.extname; return type; }; /** * Return image dimensions `[width, height]`. * * @return {Array} * @api private */ Image.prototype.size = function(){ var type = this.type() , width , height , buf , offset , blockSize , parser; function uint16(b) { return b[1] << 8 | b[0]; } function uint32(b) { return b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; } // Determine dimensions switch (type) { case 'jpeg': buf = new Buffer(this.length); fs.readSync(this.fd, buf, 0, this.length, 0); offset = 4; blockSize = buf[offset] << 8 | buf[offset + 1]; while (offset < this.length) { offset += blockSize; if (offset >= this.length || 0xff != buf[offset]) break; // SOF0 or SOF2 (progressive) if (0xc0 == buf[offset + 1] || 0xc2 == buf[offset + 1]) { height = buf[offset + 5] << 8 | buf[offset + 6]; width = buf[offset + 7] << 8 | buf[offset + 8]; } else { offset += 2; blockSize = buf[offset] << 8 | buf[offset + 1]; } } break; case 'png': buf = new Buffer(8); // IHDR chunk width / height uint32_t big-endian fs.readSync(this.fd, buf, 0, 8, 16); width = uint32(buf); height = uint32(buf.slice(4, 8)); break; case 'gif': buf = new Buffer(4); // width / height uint16_t little-endian fs.readSync(this.fd, buf, 0, 4, 6); width = uint16(buf); height = uint16(buf.slice(2, 4)); break; case 'svg': offset = Math.min(this.length, 1024); buf = new Buffer(offset); fs.readSync(this.fd, buf, 0, offset, 0); buf = buf.toString('utf8'); parser = sax.parser(true); parser.onopentag = function(node) { if ('svg' == node.name && node.attributes.width && node.attributes.height) { width = parseInt(node.attributes.width, 10); height = parseInt(node.attributes.height, 10); } }; parser.write(buf).close(); break; } if ('number' != typeof width) throw new Error('failed to find width of "' + this.path + '"'); if ('number' != typeof height) throw new Error('failed to find height of "' + this.path + '"'); return [width, height]; };