UNPKG

rehype-img-size

Version:

rehype plugin to set local image size properties to img tag

1,722 lines (1,590 loc) 49.8 kB
'use strict'; var path = require('path'); var require$$0 = require('fs'); var require$$1 = require('events'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0); var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1); /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * * @typedef {string} Type * @typedef {Object<string, unknown>} Props * * @typedef {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} Test */ const convert = /** * @type {( * (<T extends Node>(test: T['type']|Partial<T>|TestFunctionPredicate<T>) => AssertPredicate<T>) & * ((test?: Test) => AssertAnything) * )} */ ( /** * Generate an assertion from a check. * @param {Test} [test] * When nullish, checks if `node` is a `Node`. * When `string`, works like passing `function (node) {return node.type === test}`. * When `function` checks if function passed the node is true. * When `object`, checks that all keys in test are in node, and that they have (strictly) equal values. * When `array`, checks any one of the subtests pass. * @returns {AssertAnything} */ function (test) { if (test === undefined || test === null) { return ok } if (typeof test === 'string') { return typeFactory(test) } if (typeof test === 'object') { return Array.isArray(test) ? anyFactory(test) : propsFactory(test) } if (typeof test === 'function') { return castFactory(test) } throw new Error('Expected function, string, or object as test') } ); /** * @param {Array.<Type|Props|TestFunctionAnything>} tests * @returns {AssertAnything} */ function anyFactory(tests) { /** @type {Array.<AssertAnything>} */ const checks = []; let index = -1; while (++index < tests.length) { checks[index] = convert(tests[index]); } return castFactory(any) /** * @this {unknown} * @param {unknown[]} parameters * @returns {boolean} */ function any(...parameters) { let index = -1; while (++index < checks.length) { if (checks[index].call(this, ...parameters)) return true } return false } } /** * Utility to assert each property in `test` is represented in `node`, and each * values are strictly equal. * * @param {Props} check * @returns {AssertAnything} */ function propsFactory(check) { return castFactory(all) /** * @param {Node} node * @returns {boolean} */ function all(node) { /** @type {string} */ let key; for (key in check) { // @ts-expect-error: hush, it sure works as an index. if (node[key] !== check[key]) return false } return true } } /** * Utility to convert a string into a function which checks a given node’s type * for said string. * * @param {Type} check * @returns {AssertAnything} */ function typeFactory(check) { return castFactory(type) /** * @param {Node} node */ function type(node) { return node && node.type === check } } /** * Utility to convert a string into a function which checks a given node’s type * for said string. * @param {TestFunctionAnything} check * @returns {AssertAnything} */ function castFactory(check) { return assertion /** * @this {unknown} * @param {Array.<unknown>} parameters * @returns {boolean} */ function assertion(...parameters) { // @ts-expect-error: spreading is fine. return Boolean(check.call(this, ...parameters)) } } // Utility to return true. function ok() { return true } /** * @param {string} d * @returns {string} */ function color(d) { return '\u001B[33m' + d + '\u001B[39m' } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('./complex-types').Action} Action * @typedef {import('./complex-types').Index} Index * @typedef {import('./complex-types').ActionTuple} ActionTuple * @typedef {import('./complex-types').VisitorResult} VisitorResult * @typedef {import('./complex-types').Visitor} Visitor */ /** * Continue traversing as normal */ const CONTINUE = true; /** * Do not traverse this node’s children */ const SKIP = 'skip'; /** * Stop traversing immediately */ const EXIT = false; /** * Visit children of tree which pass a test * * @param tree Abstract syntax tree to walk * @param test Test node, optional * @param visitor Function to run for each node * @param reverse Visit the tree in reverse order, defaults to false */ const visitParents = /** * @type {( * (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & * (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test * @param {import('./complex-types').Visitor<Node>} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { reverse = visitor; // @ts-expect-error no visitor given, so `visitor` is test. visitor = test; test = null; } const is = convert(test); const step = reverse ? -1 : 1; factory(tree, null, [])(); /** * @param {Node} node * @param {number?} index * @param {Array.<Parent>} parents */ function factory(node, index, parents) { /** @type {Object.<string, unknown>} */ // @ts-expect-error: hush const value = typeof node === 'object' && node !== null ? node : {}; /** @type {string|undefined} */ let name; if (typeof value.type === 'string') { name = typeof value.tagName === 'string' ? value.tagName : typeof value.name === 'string' ? value.name : undefined; Object.defineProperty(visit, 'name', { value: 'node (' + color(value.type + (name ? '<' + name + '>' : '')) + ')' }); } return visit function visit() { /** @type {ActionTuple} */ let result = []; /** @type {ActionTuple} */ let subresult; /** @type {number} */ let offset; /** @type {Array.<Parent>} */ let grandparents; if (!test || is(node, index, parents[parents.length - 1] || null)) { result = toResult(visitor(node, parents)); if (result[0] === EXIT) { return result } } // @ts-expect-error looks like a parent. if (node.children && result[0] !== SKIP) { // @ts-expect-error looks like a parent. offset = (reverse ? node.children.length : -1) + step; // @ts-expect-error looks like a parent. grandparents = parents.concat(node); // @ts-expect-error looks like a parent. while (offset > -1 && offset < node.children.length) { // @ts-expect-error looks like a parent. subresult = factory(node.children[offset], offset, grandparents)(); if (subresult[0] === EXIT) { return subresult } offset = typeof subresult[1] === 'number' ? subresult[1] : offset + step; } } return result } } } ); /** * @param {VisitorResult} value * @returns {ActionTuple} */ function toResult(value) { if (Array.isArray(value)) { return value } if (typeof value === 'number') { return [CONTINUE, value] } return [value] } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult * @typedef {import('./complex-types').Visitor} Visitor */ /** * Visit children of tree which pass a test * * @param tree Abstract syntax tree to walk * @param test Test, optional * @param visitor Function to run for each node * @param reverse Fisit the tree in reverse, defaults to false */ const visit = /** * @type {( * (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & * (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test * @param {import('./complex-types').Visitor} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { reverse = visitor; visitor = test; test = null; } visitParents(tree, test, overload, reverse); /** * @param {Node} node * @param {Array.<Parent>} parents */ function overload(node, parents) { const parent = parents[parents.length - 1]; return visitor( node, parent ? parent.children.indexOf(node) : null, parent ) } } ); var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var dist = {exports: {}}; var queue = {exports: {}}; var inherits$1 = {exports: {}}; var inherits_browser = {exports: {}}; if (typeof Object.create === 'function') { // implementation from standard node.js 'util' module inherits_browser.exports = function inherits(ctor, superCtor) { if (superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); } }; } else { // old school shim for old browsers inherits_browser.exports = function inherits(ctor, superCtor) { if (superCtor) { ctor.super_ = superCtor; var TempCtor = function () {}; TempCtor.prototype = superCtor.prototype; ctor.prototype = new TempCtor(); ctor.prototype.constructor = ctor; } }; } try { var util = require('util'); /* istanbul ignore next */ if (typeof util.inherits !== 'function') throw ''; inherits$1.exports = util.inherits; } catch (e) { /* istanbul ignore next */ inherits$1.exports = inherits_browser.exports; } var inherits = inherits$1.exports; var EventEmitter = require$$1__default["default"].EventEmitter; queue.exports = Queue; queue.exports.default = Queue; function Queue (options) { if (!(this instanceof Queue)) { return new Queue(options) } EventEmitter.call(this); options = options || {}; this.concurrency = options.concurrency || Infinity; this.timeout = options.timeout || 0; this.autostart = options.autostart || false; this.results = options.results || null; this.pending = 0; this.session = 0; this.running = false; this.jobs = []; this.timers = {}; } inherits(Queue, EventEmitter); var arrayMethods = [ 'pop', 'shift', 'indexOf', 'lastIndexOf' ]; arrayMethods.forEach(function (method) { Queue.prototype[method] = function () { return Array.prototype[method].apply(this.jobs, arguments) }; }); Queue.prototype.slice = function (begin, end) { this.jobs = this.jobs.slice(begin, end); return this }; Queue.prototype.reverse = function () { this.jobs.reverse(); return this }; var arrayAddMethods = [ 'push', 'unshift', 'splice' ]; arrayAddMethods.forEach(function (method) { Queue.prototype[method] = function () { var methodResult = Array.prototype[method].apply(this.jobs, arguments); if (this.autostart) { this.start(); } return methodResult }; }); Object.defineProperty(Queue.prototype, 'length', { get: function () { return this.pending + this.jobs.length } }); Queue.prototype.start = function (cb) { if (cb) { callOnErrorOrEnd.call(this, cb); } this.running = true; if (this.pending >= this.concurrency) { return } if (this.jobs.length === 0) { if (this.pending === 0) { done.call(this); } return } var self = this; var job = this.jobs.shift(); var once = true; var session = this.session; var timeoutId = null; var didTimeout = false; var resultIndex = null; var timeout = job.hasOwnProperty('timeout') ? job.timeout : this.timeout; function next (err, result) { if (once && self.session === session) { once = false; self.pending--; if (timeoutId !== null) { delete self.timers[timeoutId]; clearTimeout(timeoutId); } if (err) { self.emit('error', err, job); } else if (didTimeout === false) { if (resultIndex !== null) { self.results[resultIndex] = Array.prototype.slice.call(arguments, 1); } self.emit('success', result, job); } if (self.session === session) { if (self.pending === 0 && self.jobs.length === 0) { done.call(self); } else if (self.running) { self.start(); } } } } if (timeout) { timeoutId = setTimeout(function () { didTimeout = true; if (self.listeners('timeout').length > 0) { self.emit('timeout', next, job); } else { next(); } }, timeout); this.timers[timeoutId] = timeoutId; } if (this.results) { resultIndex = this.results.length; this.results[resultIndex] = null; } this.pending++; self.emit('start', job); var promise = job(next); if (promise && promise.then && typeof promise.then === 'function') { promise.then(function (result) { return next(null, result) }).catch(function (err) { return next(err || true) }); } if (this.running && this.jobs.length > 0) { this.start(); } }; Queue.prototype.stop = function () { this.running = false; }; Queue.prototype.end = function (err) { clearTimers.call(this); this.jobs.length = 0; this.pending = 0; done.call(this, err); }; function clearTimers () { for (var key in this.timers) { var timeoutId = this.timers[key]; delete this.timers[key]; clearTimeout(timeoutId); } } function callOnErrorOrEnd (cb) { var self = this; this.on('error', onerror); this.on('end', onend); function onerror (err) { self.end(err); } function onend (err) { self.removeListener('error', onerror); self.removeListener('end', onend); cb(err, this.results); } } function done (err) { this.session++; this.running = false; this.emit('end', err); } var types = {}; var bmp = {}; Object.defineProperty(bmp, "__esModule", { value: true }); bmp.BMP = void 0; bmp.BMP = { validate(buffer) { return ('BM' === buffer.toString('ascii', 0, 2)); }, calculate(buffer) { return { height: Math.abs(buffer.readInt32LE(22)), width: buffer.readUInt32LE(18) }; } }; var cur = {}; var ico = {}; Object.defineProperty(ico, "__esModule", { value: true }); ico.ICO = void 0; const TYPE_ICON = 1; /** * ICON Header * * | Offset | Size | Purpose | * | 0 | 2 | Reserved. Must always be 0. | * | 2 | 2 | Image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. | * | 4 | 2 | Number of images in the file. | * */ const SIZE_HEADER$1 = 2 + 2 + 2; // 6 /** * Image Entry * * | Offset | Size | Purpose | * | 0 | 1 | Image width in pixels. Can be any number between 0 and 255. Value 0 means width is 256 pixels. | * | 1 | 1 | Image height in pixels. Can be any number between 0 and 255. Value 0 means height is 256 pixels. | * | 2 | 1 | Number of colors in the color palette. Should be 0 if the image does not use a color palette. | * | 3 | 1 | Reserved. Should be 0. | * | 4 | 2 | ICO format: Color planes. Should be 0 or 1. | * | | | CUR format: The horizontal coordinates of the hotspot in number of pixels from the left. | * | 6 | 2 | ICO format: Bits per pixel. | * | | | CUR format: The vertical coordinates of the hotspot in number of pixels from the top. | * | 8 | 4 | The size of the image's data in bytes | * | 12 | 4 | The offset of BMP or PNG data from the beginning of the ICO/CUR file | * */ const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4; // 16 function getSizeFromOffset(buffer, offset) { const value = buffer.readUInt8(offset); return value === 0 ? 256 : value; } function getImageSize$2(buffer, imageIndex) { const offset = SIZE_HEADER$1 + (imageIndex * SIZE_IMAGE_ENTRY); return { height: getSizeFromOffset(buffer, offset + 1), width: getSizeFromOffset(buffer, offset) }; } ico.ICO = { validate(buffer) { if (buffer.readUInt16LE(0) !== 0) { return false; } return buffer.readUInt16LE(2) === TYPE_ICON; }, calculate(buffer) { const nbImages = buffer.readUInt16LE(4); const imageSize = getImageSize$2(buffer, 0); if (nbImages === 1) { return imageSize; } const imgs = [imageSize]; for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) { imgs.push(getImageSize$2(buffer, imageIndex)); } const result = { height: imageSize.height, images: imgs, width: imageSize.width }; return result; } }; Object.defineProperty(cur, "__esModule", { value: true }); cur.CUR = void 0; const ico_1$1 = ico; const TYPE_CURSOR = 2; cur.CUR = { validate(buffer) { if (buffer.readUInt16LE(0) !== 0) { return false; } return buffer.readUInt16LE(2) === TYPE_CURSOR; }, calculate(buffer) { return ico_1$1.ICO.calculate(buffer); } }; var dds = {}; Object.defineProperty(dds, "__esModule", { value: true }); dds.DDS = void 0; dds.DDS = { validate(buffer) { return buffer.readUInt32LE(0) === 0x20534444; }, calculate(buffer) { return { height: buffer.readUInt32LE(12), width: buffer.readUInt32LE(16) }; } }; var gif = {}; Object.defineProperty(gif, "__esModule", { value: true }); gif.GIF = void 0; const gifRegexp = /^GIF8[79]a/; gif.GIF = { validate(buffer) { const signature = buffer.toString('ascii', 0, 6); return (gifRegexp.test(signature)); }, calculate(buffer) { return { height: buffer.readUInt16LE(8), width: buffer.readUInt16LE(6) }; } }; var icns = {}; Object.defineProperty(icns, "__esModule", { value: true }); icns.ICNS = void 0; /** * ICNS Header * * | Offset | Size | Purpose | * | 0 | 4 | Magic literal, must be "icns" (0x69, 0x63, 0x6e, 0x73) | * | 4 | 4 | Length of file, in bytes, msb first. | * */ const SIZE_HEADER = 4 + 4; // 8 const FILE_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN /** * Image Entry * * | Offset | Size | Purpose | * | 0 | 4 | Icon type, see OSType below. | * | 4 | 4 | Length of data, in bytes (including type and length), msb first. | * | 8 | n | Icon data | */ const ENTRY_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN const ICON_TYPE_SIZE = { ICON: 32, 'ICN#': 32, // m => 16 x 16 'icm#': 16, icm4: 16, icm8: 16, // s => 16 x 16 'ics#': 16, ics4: 16, ics8: 16, is32: 16, s8mk: 16, icp4: 16, // l => 32 x 32 icl4: 32, icl8: 32, il32: 32, l8mk: 32, icp5: 32, ic11: 32, // h => 48 x 48 ich4: 48, ich8: 48, ih32: 48, h8mk: 48, // . => 64 x 64 icp6: 64, ic12: 32, // t => 128 x 128 it32: 128, t8mk: 128, ic07: 128, // . => 256 x 256 ic08: 256, ic13: 256, // . => 512 x 512 ic09: 512, ic14: 512, // . => 1024 x 1024 ic10: 1024, }; function readImageHeader(buffer, imageOffset) { const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET; return [ buffer.toString('ascii', imageOffset, imageLengthOffset), buffer.readUInt32BE(imageLengthOffset) ]; } function getImageSize$1(type) { const size = ICON_TYPE_SIZE[type]; return { width: size, height: size, type }; } icns.ICNS = { validate(buffer) { return ('icns' === buffer.toString('ascii', 0, 4)); }, calculate(buffer) { const bufferLength = buffer.length; const fileLength = buffer.readUInt32BE(FILE_LENGTH_OFFSET); let imageOffset = SIZE_HEADER; let imageHeader = readImageHeader(buffer, imageOffset); let imageSize = getImageSize$1(imageHeader[0]); imageOffset += imageHeader[1]; if (imageOffset === fileLength) { return imageSize; } const result = { height: imageSize.height, images: [imageSize], width: imageSize.width }; while (imageOffset < fileLength && imageOffset < bufferLength) { imageHeader = readImageHeader(buffer, imageOffset); imageSize = getImageSize$1(imageHeader[0]); imageOffset += imageHeader[1]; result.images.push(imageSize); } return result; } }; var j2c = {}; Object.defineProperty(j2c, "__esModule", { value: true }); j2c.J2C = void 0; j2c.J2C = { validate(buffer) { // TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC return buffer.toString('hex', 0, 4) === 'ff4fff51'; }, calculate(buffer) { return { height: buffer.readUInt32BE(12), width: buffer.readUInt32BE(8), }; } }; var jp2 = {}; Object.defineProperty(jp2, "__esModule", { value: true }); jp2.JP2 = void 0; const BoxTypes = { ftyp: '66747970', ihdr: '69686472', jp2h: '6a703268', jp__: '6a502020', rreq: '72726571', xml_: '786d6c20' }; const calculateRREQLength = (box) => { const unit = box.readUInt8(0); let offset = 1 + (2 * unit); const numStdFlags = box.readUInt16BE(offset); const flagsLength = numStdFlags * (2 + unit); offset = offset + 2 + flagsLength; const numVendorFeatures = box.readUInt16BE(offset); const featuresLength = numVendorFeatures * (16 + unit); return offset + 2 + featuresLength; }; const parseIHDR = (box) => { return { height: box.readUInt32BE(4), width: box.readUInt32BE(8), }; }; jp2.JP2 = { validate(buffer) { const signature = buffer.toString('hex', 4, 8); const signatureLength = buffer.readUInt32BE(0); if (signature !== BoxTypes.jp__ || signatureLength < 1) { return false; } const ftypeBoxStart = signatureLength + 4; const ftypBoxLength = buffer.readUInt32BE(signatureLength); const ftypBox = buffer.slice(ftypeBoxStart, ftypeBoxStart + ftypBoxLength); return ftypBox.toString('hex', 0, 4) === BoxTypes.ftyp; }, calculate(buffer) { const signatureLength = buffer.readUInt32BE(0); const ftypBoxLength = buffer.readUInt16BE(signatureLength + 2); let offset = signatureLength + 4 + ftypBoxLength; const nextBoxType = buffer.toString('hex', offset, offset + 4); switch (nextBoxType) { case BoxTypes.rreq: // WHAT ARE THESE 4 BYTES????? // eslint-disable-next-line no-case-declarations const MAGIC = 4; offset = offset + 4 + MAGIC + calculateRREQLength(buffer.slice(offset + 4)); return parseIHDR(buffer.slice(offset + 8, offset + 24)); case BoxTypes.jp2h: return parseIHDR(buffer.slice(offset + 8, offset + 24)); default: throw new TypeError('Unsupported header found: ' + buffer.toString('ascii', offset, offset + 4)); } } }; var jpg = {}; var readUInt$1 = {}; Object.defineProperty(readUInt$1, "__esModule", { value: true }); readUInt$1.readUInt = void 0; // Abstract reading multi-byte unsigned integers function readUInt(buffer, bits, offset, isBigEndian) { offset = offset || 0; const endian = isBigEndian ? 'BE' : 'LE'; const methodName = ('readUInt' + bits + endian); return buffer[methodName].call(buffer, offset); } readUInt$1.readUInt = readUInt; // NOTE: we only support baseline and progressive JPGs here // due to the structure of the loader class, we only get a buffer // with a maximum size of 4096 bytes. so if the SOF marker is outside // if this range we can't detect the file size correctly. Object.defineProperty(jpg, "__esModule", { value: true }); jpg.JPG = void 0; const readUInt_1$1 = readUInt$1; const EXIF_MARKER = '45786966'; const APP1_DATA_SIZE_BYTES = 2; const EXIF_HEADER_BYTES = 6; const TIFF_BYTE_ALIGN_BYTES = 2; const BIG_ENDIAN_BYTE_ALIGN = '4d4d'; const LITTLE_ENDIAN_BYTE_ALIGN = '4949'; // Each entry is exactly 12 bytes const IDF_ENTRY_BYTES = 12; const NUM_DIRECTORY_ENTRIES_BYTES = 2; function isEXIF(buffer) { return (buffer.toString('hex', 2, 6) === EXIF_MARKER); } function extractSize(buffer, index) { return { height: buffer.readUInt16BE(index), width: buffer.readUInt16BE(index + 2) }; } function extractOrientation(exifBlock, isBigEndian) { // TODO: assert that this contains 0x002A // let STATIC_MOTOROLA_TIFF_HEADER_BYTES = 2 // let TIFF_IMAGE_FILE_DIRECTORY_BYTES = 4 // TODO: derive from TIFF_IMAGE_FILE_DIRECTORY_BYTES const idfOffset = 8; // IDF osset works from right after the header bytes // (so the offset includes the tiff byte align) const offset = EXIF_HEADER_BYTES + idfOffset; const idfDirectoryEntries = readUInt_1$1.readUInt(exifBlock, 16, offset, isBigEndian); for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) { const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + (directoryEntryNumber * IDF_ENTRY_BYTES); const end = start + IDF_ENTRY_BYTES; // Skip on corrupt EXIF blocks if (start > exifBlock.length) { return; } const block = exifBlock.slice(start, end); const tagNumber = readUInt_1$1.readUInt(block, 16, 0, isBigEndian); // 0x0112 (decimal: 274) is the `orientation` tag ID if (tagNumber === 274) { const dataFormat = readUInt_1$1.readUInt(block, 16, 2, isBigEndian); if (dataFormat !== 3) { return; } // unsinged int has 2 bytes per component // if there would more than 4 bytes in total it's a pointer const numberOfComponents = readUInt_1$1.readUInt(block, 32, 4, isBigEndian); if (numberOfComponents !== 1) { return; } return readUInt_1$1.readUInt(block, 16, 8, isBigEndian); } } } function validateExifBlock(buffer, index) { // Skip APP1 Data Size const exifBlock = buffer.slice(APP1_DATA_SIZE_BYTES, index); // Consider byte alignment const byteAlign = exifBlock.toString('hex', EXIF_HEADER_BYTES, EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES); // Ignore Empty EXIF. Validate byte alignment const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN; const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN; if (isBigEndian || isLittleEndian) { return extractOrientation(exifBlock, isBigEndian); } } function validateBuffer(buffer, index) { // index should be within buffer limits if (index > buffer.length) { throw new TypeError('Corrupt JPG, exceeded buffer limits'); } // Every JPEG block must begin with a 0xFF if (buffer[index] !== 0xFF) { throw new TypeError('Invalid JPG, marker table corrupted'); } } jpg.JPG = { validate(buffer) { const SOIMarker = buffer.toString('hex', 0, 2); return ('ffd8' === SOIMarker); }, calculate(buffer) { // Skip 4 chars, they are for signature buffer = buffer.slice(4); let orientation; let next; while (buffer.length) { // read length of the next block const i = buffer.readUInt16BE(0); if (isEXIF(buffer)) { orientation = validateExifBlock(buffer, i); } // ensure correct format validateBuffer(buffer, i); // 0xFFC0 is baseline standard(SOF) // 0xFFC1 is baseline optimized(SOF) // 0xFFC2 is progressive(SOF2) next = buffer[i + 1]; if (next === 0xC0 || next === 0xC1 || next === 0xC2) { const size = extractSize(buffer, i + 5); // TODO: is orientation=0 a valid answer here? if (!orientation) { return size; } return { height: size.height, orientation, width: size.width }; } // move to the next block buffer = buffer.slice(i + 2); } throw new TypeError('Invalid JPG, no size found'); } }; var ktx = {}; Object.defineProperty(ktx, "__esModule", { value: true }); ktx.KTX = void 0; const SIGNATURE = 'KTX 11'; ktx.KTX = { validate(buffer) { return SIGNATURE === buffer.toString('ascii', 1, 7); }, calculate(buffer) { return { height: buffer.readUInt32LE(40), width: buffer.readUInt32LE(36), }; } }; var png = {}; Object.defineProperty(png, "__esModule", { value: true }); png.PNG = void 0; const pngSignature = 'PNG\r\n\x1a\n'; const pngImageHeaderChunkName = 'IHDR'; // Used to detect "fried" png's: http://www.jongware.com/pngdefry.html const pngFriedChunkName = 'CgBI'; png.PNG = { validate(buffer) { if (pngSignature === buffer.toString('ascii', 1, 8)) { let chunkName = buffer.toString('ascii', 12, 16); if (chunkName === pngFriedChunkName) { chunkName = buffer.toString('ascii', 28, 32); } if (chunkName !== pngImageHeaderChunkName) { throw new TypeError('Invalid PNG'); } return true; } return false; }, calculate(buffer) { if (buffer.toString('ascii', 12, 16) === pngFriedChunkName) { return { height: buffer.readUInt32BE(36), width: buffer.readUInt32BE(32) }; } return { height: buffer.readUInt32BE(20), width: buffer.readUInt32BE(16) }; } }; var pnm = {}; Object.defineProperty(pnm, "__esModule", { value: true }); pnm.PNM = void 0; const PNMTypes = { P1: 'pbm/ascii', P2: 'pgm/ascii', P3: 'ppm/ascii', P4: 'pbm', P5: 'pgm', P6: 'ppm', P7: 'pam', PF: 'pfm' }; const Signatures = Object.keys(PNMTypes); const handlers = { default: (lines) => { let dimensions = []; while (lines.length > 0) { const line = lines.shift(); if (line[0] === '#') { continue; } dimensions = line.split(' '); break; } if (dimensions.length === 2) { return { height: parseInt(dimensions[1], 10), width: parseInt(dimensions[0], 10), }; } else { throw new TypeError('Invalid PNM'); } }, pam: (lines) => { const size = {}; while (lines.length > 0) { const line = lines.shift(); if (line.length > 16 || line.charCodeAt(0) > 128) { continue; } const [key, value] = line.split(' '); if (key && value) { size[key.toLowerCase()] = parseInt(value, 10); } if (size.height && size.width) { break; } } if (size.height && size.width) { return { height: size.height, width: size.width }; } else { throw new TypeError('Invalid PAM'); } } }; pnm.PNM = { validate(buffer) { const signature = buffer.toString('ascii', 0, 2); return Signatures.includes(signature); }, calculate(buffer) { const signature = buffer.toString('ascii', 0, 2); const type = PNMTypes[signature]; // TODO: this probably generates garbage. move to a stream based parser const lines = buffer.toString('ascii', 3).split(/[\r\n]+/); const handler = handlers[type] || handlers.default; return handler(lines); } }; var psd = {}; Object.defineProperty(psd, "__esModule", { value: true }); psd.PSD = void 0; psd.PSD = { validate(buffer) { return ('8BPS' === buffer.toString('ascii', 0, 4)); }, calculate(buffer) { return { height: buffer.readUInt32BE(14), width: buffer.readUInt32BE(18) }; } }; var svg = {}; Object.defineProperty(svg, "__esModule", { value: true }); svg.SVG = void 0; const svgReg = /<svg\s([^>"']|"[^"]*"|'[^']*')*>/; const extractorRegExps = { height: /\sheight=(['"])([^%]+?)\1/, root: svgReg, viewbox: /\sviewBox=(['"])(.+?)\1/i, width: /\swidth=(['"])([^%]+?)\1/, }; const INCH_CM = 2.54; const units = { in: 96, cm: 96 / INCH_CM, em: 16, ex: 8, m: 96 / INCH_CM * 100, mm: 96 / INCH_CM / 10, pc: 96 / 72 / 12, pt: 96 / 72, px: 1 }; const unitsReg = new RegExp(`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join('|')})?$`); function parseLength(len) { const m = unitsReg.exec(len); if (!m) { return undefined; } return Math.round(Number(m[1]) * (units[m[2]] || 1)); } function parseViewbox(viewbox) { const bounds = viewbox.split(' '); return { height: parseLength(bounds[3]), width: parseLength(bounds[2]) }; } function parseAttributes(root) { const width = root.match(extractorRegExps.width); const height = root.match(extractorRegExps.height); const viewbox = root.match(extractorRegExps.viewbox); return { height: height && parseLength(height[2]), viewbox: viewbox && parseViewbox(viewbox[2]), width: width && parseLength(width[2]), }; } function calculateByDimensions(attrs) { return { height: attrs.height, width: attrs.width, }; } function calculateByViewbox(attrs, viewbox) { const ratio = viewbox.width / viewbox.height; if (attrs.width) { return { height: Math.floor(attrs.width / ratio), width: attrs.width, }; } if (attrs.height) { return { height: attrs.height, width: Math.floor(attrs.height * ratio), }; } return { height: viewbox.height, width: viewbox.width, }; } svg.SVG = { validate(buffer) { const str = String(buffer); return svgReg.test(str); }, calculate(buffer) { const root = buffer.toString('utf8').match(extractorRegExps.root); if (root) { const attrs = parseAttributes(root[0]); if (attrs.width && attrs.height) { return calculateByDimensions(attrs); } if (attrs.viewbox) { return calculateByViewbox(attrs, attrs.viewbox); } } throw new TypeError('Invalid SVG'); } }; var tiff = {}; Object.defineProperty(tiff, "__esModule", { value: true }); tiff.TIFF = void 0; // based on http://www.compix.com/fileformattif.htm // TO-DO: support big-endian as well const fs = require$$0__default["default"]; const readUInt_1 = readUInt$1; // Read IFD (image-file-directory) into a buffer function readIFD(buffer, filepath, isBigEndian) { const ifdOffset = readUInt_1.readUInt(buffer, 32, 4, isBigEndian); // read only till the end of the file let bufferSize = 1024; const fileSize = fs.statSync(filepath).size; if (ifdOffset + bufferSize > fileSize) { bufferSize = fileSize - ifdOffset - 10; } // populate the buffer const endBuffer = Buffer.alloc(bufferSize); const descriptor = fs.openSync(filepath, 'r'); fs.readSync(descriptor, endBuffer, 0, bufferSize, ifdOffset); fs.closeSync(descriptor); return endBuffer.slice(2); } // TIFF values seem to be messed up on Big-Endian, this helps function readValue(buffer, isBigEndian) { const low = readUInt_1.readUInt(buffer, 16, 8, isBigEndian); const high = readUInt_1.readUInt(buffer, 16, 10, isBigEndian); return (high << 16) + low; } // move to the next tag function nextTag(buffer) { if (buffer.length > 24) { return buffer.slice(12); } } // Extract IFD tags from TIFF metadata function extractTags(buffer, isBigEndian) { const tags = {}; let temp = buffer; while (temp && temp.length) { const code = readUInt_1.readUInt(temp, 16, 0, isBigEndian); const type = readUInt_1.readUInt(temp, 16, 2, isBigEndian); const length = readUInt_1.readUInt(temp, 32, 4, isBigEndian); // 0 means end of IFD if (code === 0) { break; } else { // 256 is width, 257 is height // if (code === 256 || code === 257) { if (length === 1 && (type === 3 || type === 4)) { tags[code] = readValue(temp, isBigEndian); } // move to the next tag temp = nextTag(temp); } } return tags; } // Test if the TIFF is Big Endian or Little Endian function determineEndianness(buffer) { const signature = buffer.toString('ascii', 0, 2); if ('II' === signature) { return 'LE'; } else if ('MM' === signature) { return 'BE'; } } const signatures = [ // '492049', // currently not supported '49492a00', '4d4d002a', // Big Endian // '4d4d002a', // BigTIFF > 4GB. currently not supported ]; tiff.TIFF = { validate(buffer) { return signatures.includes(buffer.toString('hex', 0, 4)); }, calculate(buffer, filepath) { if (!filepath) { throw new TypeError('Tiff doesn\'t support buffer'); } // Determine BE/LE const isBigEndian = determineEndianness(buffer) === 'BE'; // read the IFD const ifdBuffer = readIFD(buffer, filepath, isBigEndian); // extract the tags from the IFD const tags = extractTags(ifdBuffer, isBigEndian); const width = tags[256]; const height = tags[257]; if (!width || !height) { throw new TypeError('Invalid Tiff. Missing tags'); } return { height, width }; } }; var webp = {}; Object.defineProperty(webp, "__esModule", { value: true }); webp.WEBP = void 0; function calculateExtended(buffer) { return { height: 1 + buffer.readUIntLE(7, 3), width: 1 + buffer.readUIntLE(4, 3) }; } function calculateLossless(buffer) { return { height: 1 + (((buffer[4] & 0xF) << 10) | (buffer[3] << 2) | ((buffer[2] & 0xC0) >> 6)), width: 1 + (((buffer[2] & 0x3F) << 8) | buffer[1]) }; } function calculateLossy(buffer) { // `& 0x3fff` returns the last 14 bits // TO-DO: include webp scaling in the calculations return { height: buffer.readInt16LE(8) & 0x3fff, width: buffer.readInt16LE(6) & 0x3fff }; } webp.WEBP = { validate(buffer) { const riffHeader = 'RIFF' === buffer.toString('ascii', 0, 4); const webpHeader = 'WEBP' === buffer.toString('ascii', 8, 12); const vp8Header = 'VP8' === buffer.toString('ascii', 12, 15); return (riffHeader && webpHeader && vp8Header); }, calculate(buffer) { const chunkHeader = buffer.toString('ascii', 12, 16); buffer = buffer.slice(20, 30); // Extended webp stream signature if (chunkHeader === 'VP8X') { const extendedHeader = buffer[0]; const validStart = (extendedHeader & 0xc0) === 0; const validEnd = (extendedHeader & 0x01) === 0; if (validStart && validEnd) { return calculateExtended(buffer); } else { // TODO: breaking change throw new TypeError('Invalid WebP'); } } // Lossless webp stream signature if (chunkHeader === 'VP8 ' && buffer[0] !== 0x2f) { return calculateLossy(buffer); } // Lossy webp stream signature const signature = buffer.toString('hex', 3, 6); if (chunkHeader === 'VP8L' && signature !== '9d012a') { return calculateLossless(buffer); } throw new TypeError('Invalid WebP'); } }; Object.defineProperty(types, "__esModule", { value: true }); types.typeHandlers = void 0; // load all available handlers explicitely for browserify support const bmp_1 = bmp; const cur_1 = cur; const dds_1 = dds; const gif_1 = gif; const icns_1 = icns; const ico_1 = ico; const j2c_1 = j2c; const jp2_1 = jp2; const jpg_1 = jpg; const ktx_1 = ktx; const png_1 = png; const pnm_1 = pnm; const psd_1 = psd; const svg_1 = svg; const tiff_1 = tiff; const webp_1 = webp; types.typeHandlers = { bmp: bmp_1.BMP, cur: cur_1.CUR, dds: dds_1.DDS, gif: gif_1.GIF, icns: icns_1.ICNS, ico: ico_1.ICO, j2c: j2c_1.J2C, jp2: jp2_1.JP2, jpg: jpg_1.JPG, ktx: ktx_1.KTX, png: png_1.PNG, pnm: pnm_1.PNM, psd: psd_1.PSD, svg: svg_1.SVG, tiff: tiff_1.TIFF, webp: webp_1.WEBP, }; var detector$1 = {}; Object.defineProperty(detector$1, "__esModule", { value: true }); detector$1.detector = void 0; const types_1 = types; const keys = Object.keys(types_1.typeHandlers); // This map helps avoid validating for every single image type const firstBytes = { 0x38: 'psd', 0x42: 'bmp', 0x44: 'dds', 0x47: 'gif', 0x49: 'tiff', 0x4d: 'tiff', 0x52: 'webp', 0x69: 'icns', 0x89: 'png', 0xff: 'jpg' }; function detector(buffer) { const byte = buffer[0]; if (byte in firstBytes) { const type = firstBytes[byte]; if (type && types_1.typeHandlers[type].validate(buffer)) { return type; } } const finder = (key) => types_1.typeHandlers[key].validate(buffer); return keys.find(finder); } detector$1.detector = detector; (function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.types = exports.setConcurrency = exports.disableTypes = exports.disableFS = exports.imageSize = void 0; const fs = require$$0__default["default"]; const path = path__default["default"]; const queue_1 = queue.exports; const types_1 = types; const detector_1 = detector$1; // Maximum buffer size, with a default of 512 kilobytes. // TO-DO: make this adaptive based on the initial signature of the image const MaxBufferSize = 512 * 1024; // This queue is for async `fs` operations, to avoid reaching file-descriptor limits const queue$1 = new queue_1.default({ concurrency: 100, autostart: true }); const globalOptions = { disabledFS: false, disabledTypes: [] }; /** * Return size information based on a buffer * * @param {Buffer} buffer * @param {String} filepath * @returns {Object} */ function lookup(buffer, filepath) { // detect the file type.. don't rely on the extension const type = detector_1.detector(buffer); if (typeof type !== 'undefined') { if (globalOptions.disabledTypes.indexOf(type) > -1) { throw new TypeError('disabled file type: ' + type); } // find an appropriate handler for this file type if (type in types_1.typeHandlers) { const size = types_1.typeHandlers[type].calculate(buffer, filepath); if (size !== undefined) { size.type = type; return size; } } } // throw up, if we don't understand the file throw new TypeError('unsupported file type: ' + type + ' (file: ' + filepath + ')'); } /** * Reads a file into a buffer. * @param {String} filepath * @returns {Promise<Buffer>} */ function asyncFileToBuffer(filepath) { return __awaiter(this, void 0, void 0, function* () { const handle = yield fs.promises.open(filepath, 'r'); const { size } = yield handle.stat(); if (size <= 0) { yield handle.close(); throw new Error('Empty file'); } const bufferSize = Math.min(size, MaxBufferSize); const buffer = Buffer.alloc(bufferSize); yield handle.read(buffer, 0, bufferSize, 0); yield handle.close(); return buffer; }); } /** * Synchronously reads a file into a buffer, blocking the nodejs process. * * @param {String} filepath * @returns {Buffer} */ function syncFileToBuffer(filepath) { // read from the file, synchronously const descriptor = fs.openSync(filepath, 'r'); const { size } = fs.fstatSync(descriptor); if (size <= 0) { fs.closeSync(descriptor); throw new Error('Empty file'); } const bufferSize = Math.min(size, MaxBufferSize); const buffer = Buffer.alloc(bufferSize); fs.readSync(descriptor, buffer, 0, bufferSize, 0); fs.closeSync(descriptor); return buffer; } // eslint-disable-next-line @typescript-eslint/no-use-before-define module.exports = exports = imageSize; // backwards compatibility exports.default = imageSize; /** * @param {Buffer|string} input - buffer or relative/absolute path of the image file * @param {Function=} [callback] - optional function for async detection */ function imageSize(input, callback) { // Handle buffer input if (Buffer.isBuffer(input)) { return lookup(input); } // input should be a string at this point if (typeof input !== 'string' || globalOptions.disabledFS) { throw new TypeError('invalid invocation. input should be a Buffer'); } // resolve the file path const filepath = path.resolve(input); if (typeof callback === 'function') { queue$1.push(() => asyncFileToBuffer(filepath) .then((buffer) => process.nextTick(callback, null, lookup(buffer, filepath))) .catch(callback)); } else { const buffer = syncFileToBuffer(filepath); return lookup(buffer, filepath); } } exports.imageSize = imageSize; const disableFS = (v) => { globalOptions.disabledFS = v; }; exports.disableFS = disableFS; const disableTypes = (types) => { globalOptions.disabledTypes = types; }; exports.disableTypes = disableTypes; const setConcurrency = (c) => { queue$1.concurrency = c; }; exports.setConcurrency = setConcurrency; exports.types = Object.keys(types_1.typeHandlers); }(dist, dist.exports)); var sizeOf = /*@__PURE__*/getDefaultExportFromCjs(dist.exports); /** * Handles: * "//" * "http://" * "https://" * "ftp://" */ const absolutePathRegex = /^(?:[a-z]+:)?\/\//; function getImageSize(src, dir) { if (absolutePathRegex.exec(src)) { return } // Treat `/` as a relative path, according to the server const shouldJoin = !path__default["default"].isAbsolute(src) || src.startsWith('/'); if (dir && shouldJoin) { src = path__default["default"].join(dir, src); } return sizeOf(src) } function setImageSize(options) { const opts = options || {}; const dir = opts.dir; return transformer function transformer(tree, file) { visit(tree, 'element', visitor); function visitor(node) { if (node.tagName === 'img') { const src = node.properties.src; const dimensions = getImageSize(src, dir) || {}; node.properties.width = dimensions.width; node.properties.height = dimensions.height; } } } } module.exports = setImageSize;