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