image.js 3.69 KB


/*!
 * Stylus - plugin - url
 * Copyright (c) Automattic <developer.wordpress.com>
 * 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];
};