image.js 3.7 KB

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