rehype-img-size
Version:
rehype plugin to set local image size properties to img tag
1,722 lines (1,590 loc) • 49.8 kB
JavaScript
;
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;