image.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /*!
  2. * Stylus - plugin - url
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var utils = require('../utils')
  10. , nodes = require('../nodes')
  11. , fs = require('fs')
  12. , path = require('path')
  13. , sax = require('sax');
  14. /**
  15. * Initialize a new `Image` with the given `ctx` and `path.
  16. *
  17. * @param {Evaluator} ctx
  18. * @param {String} path
  19. * @api private
  20. */
  21. var Image = module.exports = function Image(ctx, path) {
  22. this.ctx = ctx;
  23. this.path = utils.lookup(path, ctx.paths);
  24. if (!this.path) throw new Error('failed to locate file ' + path);
  25. };
  26. /**
  27. * Open the image for reading.
  28. *
  29. * @api private
  30. */
  31. Image.prototype.open = function(){
  32. this.fd = fs.openSync(this.path, 'r');
  33. this.length = fs.fstatSync(this.fd).size;
  34. this.extname = path.extname(this.path).slice(1);
  35. };
  36. /**
  37. * Close the file.
  38. *
  39. * @api private
  40. */
  41. Image.prototype.close = function(){
  42. if (this.fd) fs.closeSync(this.fd);
  43. };
  44. /**
  45. * Return the type of image, supports:
  46. *
  47. * - gif
  48. * - png
  49. * - jpeg
  50. * - svg
  51. *
  52. * @return {String}
  53. * @api private
  54. */
  55. Image.prototype.type = function(){
  56. var type
  57. , buf = new Buffer(4);
  58. fs.readSync(this.fd, buf, 0, 4, 0);
  59. // GIF
  60. if (0x47 == buf[0] && 0x49 == buf[1] && 0x46 == buf[2]) type = 'gif';
  61. // PNG
  62. else if (0x50 == buf[1] && 0x4E == buf[2] && 0x47 == buf[3]) type = 'png';
  63. // JPEG
  64. else if (0xff == buf[0] && 0xd8 == buf[1]) type = 'jpeg';
  65. // SVG
  66. else if ('svg' == this.extname) type = this.extname;
  67. return type;
  68. };
  69. /**
  70. * Return image dimensions `[width, height]`.
  71. *
  72. * @return {Array}
  73. * @api private
  74. */
  75. Image.prototype.size = function(){
  76. var type = this.type()
  77. , width
  78. , height
  79. , buf
  80. , offset
  81. , blockSize
  82. , parser;
  83. function uint16(b) { return b[1] << 8 | b[0]; }
  84. function uint32(b) { return b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; }
  85. // Determine dimensions
  86. switch (type) {
  87. case 'jpeg':
  88. buf = new Buffer(this.length);
  89. fs.readSync(this.fd, buf, 0, this.length, 0);
  90. offset = 4;
  91. blockSize = buf[offset] << 8 | buf[offset + 1];
  92. while (offset < this.length) {
  93. offset += blockSize;
  94. if (offset >= this.length || 0xff != buf[offset]) break;
  95. // SOF0 or SOF2 (progressive)
  96. if (0xc0 == buf[offset + 1] || 0xc2 == buf[offset + 1]) {
  97. height = buf[offset + 5] << 8 | buf[offset + 6];
  98. width = buf[offset + 7] << 8 | buf[offset + 8];
  99. } else {
  100. offset += 2;
  101. blockSize = buf[offset] << 8 | buf[offset + 1];
  102. }
  103. }
  104. break;
  105. case 'png':
  106. buf = new Buffer(8);
  107. // IHDR chunk width / height uint32_t big-endian
  108. fs.readSync(this.fd, buf, 0, 8, 16);
  109. width = uint32(buf);
  110. height = uint32(buf.slice(4, 8));
  111. break;
  112. case 'gif':
  113. buf = new Buffer(4);
  114. // width / height uint16_t little-endian
  115. fs.readSync(this.fd, buf, 0, 4, 6);
  116. width = uint16(buf);
  117. height = uint16(buf.slice(2, 4));
  118. break;
  119. case 'svg':
  120. offset = Math.min(this.length, 1024);
  121. buf = new Buffer(offset);
  122. fs.readSync(this.fd, buf, 0, offset, 0);
  123. buf = buf.toString('utf8');
  124. parser = sax.parser(true);
  125. parser.onopentag = function(node) {
  126. if ('svg' == node.name && node.attributes.width && node.attributes.height) {
  127. width = parseInt(node.attributes.width, 10);
  128. height = parseInt(node.attributes.height, 10);
  129. }
  130. };
  131. parser.write(buf).close();
  132. break;
  133. }
  134. if ('number' != typeof width) throw new Error('failed to find width of "' + this.path + '"');
  135. if ('number' != typeof height) throw new Error('failed to find height of "' + this.path + '"');
  136. return [width, height];
  137. };