mirror of
https://github.com/withastro/astro.git
synced 2025-03-31 23:31:30 -05:00
Add inferSize
to getImage so width and height are optional for remote images (#9976)
* add remote image dimension probing, and param for getImage options, and new astro error * add inferSize parameter to getImage, which probes remote image size, and respective types to picture and image component * add fixture and tests * add changeset * attempt to fix pnpm-lock.yaml * fix pnpm-lock.yaml again * pnpm-lock spacing * fix pnpm-lock AGAIN * better description of error for docgen * improve failed to probe error message and correct required dimensions error * increase timeout for mdx tests * increasing mdx timeout to 2min to see if it passes, will reduce if it does * setting mdx timeout to 70 seconds * Update packages/astro/src/assets/services/service.ts Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> * refactor to move logic to getImage instead of validateOptions and to not add a third type to image and picture * fix broken link created by docs PR * remove the probe-image-size library due to its use of node APIs * undo all changes to service.ts that were left after moving inferSize logic to getImage * update error message * remove probe-image-size library all together, update error message, add vendored version of complete image-size library instead of refactored version * Update .changeset/tame-cameras-change.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/tame-cameras-change.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * reword the error message based on Sarah's previous review * remove probe-image-size from pnpmlock * fix lockfile? * update error message name * move image-size into vendor folder * add eslint ignore to a line in image-size * test if change to mdx test timeout was needed * Update .changeset/tame-cameras-change.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * update changset syntax * patch parse heif to account for filetype block being out of order --------- Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
787e6f5247
commit
91f75afbc6
40 changed files with 1414 additions and 69 deletions
18
.changeset/tame-cameras-change.md
Normal file
18
.changeset/tame-cameras-change.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
"astro": minor
|
||||
---
|
||||
|
||||
Adds a new optional `astro:assets` image attribute `inferSize` for use with remote images.
|
||||
|
||||
Remote images can now have their dimensions inferred just like local images. Setting `inferSize` to `true` allows you to use `getImage()` and the `<Image />` and `<Picture />` components without setting the `width` and `height` properties.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { Image, Picture, getImage } from 'astro:assets';
|
||||
const myPic = await getImage({ src: "https://example.com/example.png", inferSize: true })
|
||||
---
|
||||
<Image src="https://example.com/example.png" inferSize alt="" />
|
||||
<Picture src="https://example.com/example.png" inferSize alt="" />
|
||||
```
|
||||
|
||||
Read more about [using `inferSize` with remote images](https://docs.astro.build/en/guides/images/#infersize) in our documentation.
|
|
@ -1,5 +1,9 @@
|
|||
---
|
||||
import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
|
||||
import {
|
||||
getImage,
|
||||
type LocalImageProps,
|
||||
type RemoteImageProps,
|
||||
} from 'astro:assets';
|
||||
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
||||
import type { HTMLAttributes } from '../types';
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
---
|
||||
import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
|
||||
import {
|
||||
getImage,
|
||||
type LocalImageProps,
|
||||
type RemoteImageProps,
|
||||
} from 'astro:assets';
|
||||
import type { GetImageResult, ImageOutputFormat } from '../dist/@types/astro';
|
||||
import { isESMImportedImage } from '../dist/assets/utils/imageKind';
|
||||
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
||||
|
|
|
@ -162,7 +162,6 @@
|
|||
"p-queue": "^8.0.1",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"preferred-pm": "^3.1.2",
|
||||
"probe-image-size": "^7.2.3",
|
||||
"prompts": "^2.4.2",
|
||||
"rehype": "^13.0.1",
|
||||
"resolve": "^1.22.4",
|
||||
|
|
|
@ -9,6 +9,7 @@ import type {
|
|||
UnresolvedImageTransform,
|
||||
} from './types.js';
|
||||
import { isESMImportedImage, isRemoteImage } from './utils/imageKind.js';
|
||||
import { probe } from "./utils/remoteProbe.js"
|
||||
|
||||
export async function getConfiguredImageService(): Promise<ImageService> {
|
||||
if (!globalThis?.astroAsset?.imageService) {
|
||||
|
@ -61,6 +62,21 @@ export async function getImage(
|
|||
: options.src,
|
||||
};
|
||||
|
||||
// Infer size for remote images if inferSize is true
|
||||
if (options.inferSize && isRemoteImage(resolvedOptions.src)) {
|
||||
try {
|
||||
const result = await probe(resolvedOptions.src); // Directly probe the image URL
|
||||
resolvedOptions.width ??= result.width;
|
||||
resolvedOptions.height ??= result.height;
|
||||
delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes
|
||||
} catch {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.FailedToFetchRemoteImageDimensions,
|
||||
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(resolvedOptions.src),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const originalPath = isESMImportedImage(resolvedOptions.src)
|
||||
? resolvedOptions.src.fsPath
|
||||
: resolvedOptions.src;
|
||||
|
|
|
@ -59,6 +59,7 @@ export type SrcSetValue = UnresolvedSrcSetValue & {
|
|||
*/
|
||||
export type UnresolvedImageTransform = Omit<ImageTransform, 'src'> & {
|
||||
src: ImageMetadata | string | Promise<{ default: ImageMetadata }>;
|
||||
inferSize?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -176,16 +177,38 @@ export type LocalImageProps<T> = ImageSharedProps<T> & {
|
|||
quality?: ImageQuality;
|
||||
};
|
||||
|
||||
export type RemoteImageProps<T> = WithRequired<ImageSharedProps<T>, 'width' | 'height'> & {
|
||||
/**
|
||||
* URL of a remote image. Can start with a protocol (ex: `https://`) or alternatively `/`, or `Astro.url`, for images in the `public` folder
|
||||
*
|
||||
* Remote images are not optimized, and require both `width` and `height` to be set.
|
||||
*
|
||||
* **Example**:
|
||||
* ```
|
||||
* <Image src="https://example.com/image.png" width={450} height={300} alt="..." />
|
||||
* ```
|
||||
*/
|
||||
src: string;
|
||||
};
|
||||
export type RemoteImageProps<T> =
|
||||
| (ImageSharedProps<T> & {
|
||||
/**
|
||||
* URL of a remote image. Can start with a protocol (ex: `https://`) or alternatively `/`, or `Astro.url`, for images in the `public` folder
|
||||
*
|
||||
* Remote images are not optimized, and require both `width` and `height` to be set.
|
||||
*
|
||||
* **Example**:
|
||||
* ```
|
||||
* <Image src="https://example.com/image.png" width={450} height={300} alt="..." />
|
||||
* ```
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* When inferSize is true width and height are not required
|
||||
*/
|
||||
inferSize: true;
|
||||
})
|
||||
| (WithRequired<ImageSharedProps<T>, 'width' | 'height'> & {
|
||||
/**
|
||||
* URL of a remote image. Can start with a protocol (ex: `https://`) or alternatively `/`, or `Astro.url`, for images in the `public` folder
|
||||
*
|
||||
* Remote images are not optimized, and require both `width` and `height` to be set.
|
||||
*
|
||||
* **Example**:
|
||||
* ```
|
||||
* <Image src="https://example.com/image.png" width={450} height={300} alt="..." />
|
||||
* ```
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* When inferSize is false or undefined width and height are required
|
||||
*/
|
||||
inferSize?: false | undefined;
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import probe from 'probe-image-size';
|
||||
|
||||
import { lookup as probe } from '../utils/vendor/image-size/lookup.js'
|
||||
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
||||
import type { ImageInputFormat, ImageMetadata } from '../types.js';
|
||||
|
||||
|
@ -6,10 +7,9 @@ export async function imageMetadata(
|
|||
data: Uint8Array,
|
||||
src?: string
|
||||
): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>> {
|
||||
// @ts-expect-error probe-image-size types are wrong, it does accept Uint8Array. From the README: "Sync version can eat arrays, typed arrays and buffers."
|
||||
const result = probe.sync(data);
|
||||
const result = probe(data);
|
||||
|
||||
if (result === null) {
|
||||
if (!result.height || !result.width || !result.type ) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.NoImageMetadata,
|
||||
message: AstroErrorData.NoImageMetadata.message(src),
|
||||
|
|
48
packages/astro/src/assets/utils/remoteProbe.ts
Normal file
48
packages/astro/src/assets/utils/remoteProbe.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
import { lookup } from './vendor/image-size/lookup.js'
|
||||
import type { ISize } from './vendor/image-size/types/interface.ts';
|
||||
|
||||
export async function probe(url: string): Promise<ISize> {
|
||||
// Start fetching the image
|
||||
const response = await fetch(url);
|
||||
if (!response.body || !response.ok) {
|
||||
throw new Error('Failed to fetch image');
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
|
||||
let done: boolean | undefined, value: Uint8Array;
|
||||
let accumulatedChunks = new Uint8Array();
|
||||
|
||||
// Process the stream chunk by chunk
|
||||
while (!done) {
|
||||
const readResult = await reader.read();
|
||||
done = readResult.done;
|
||||
|
||||
if (done) break;
|
||||
|
||||
if (readResult.value) {
|
||||
value = readResult.value;
|
||||
|
||||
// Accumulate chunks
|
||||
let tmp = new Uint8Array(accumulatedChunks.length + value.length);
|
||||
tmp.set(accumulatedChunks, 0);
|
||||
tmp.set(value, accumulatedChunks.length);
|
||||
accumulatedChunks = tmp;
|
||||
|
||||
try {
|
||||
// Attempt to determine the size with each new chunk
|
||||
const dimensions = lookup(accumulatedChunks);
|
||||
if (dimensions) {
|
||||
await reader.cancel(); // stop stream as we have size now
|
||||
return dimensions;
|
||||
}
|
||||
} catch (error) {
|
||||
// This catch block is specifically for `sizeOf` failures,
|
||||
// which might occur if the accumulated data isn't yet sufficient.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to parse the size');
|
||||
}
|
9
packages/astro/src/assets/utils/vendor/image-size/LICENSE
vendored
Normal file
9
packages/astro/src/assets/utils/vendor/image-size/LICENSE
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2013-Present Aditya Yadav, http://netroy.in
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
packages/astro/src/assets/utils/vendor/image-size/README.md
vendored
Normal file
3
packages/astro/src/assets/utils/vendor/image-size/README.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
This code comes from https://github.com/image-size/image-size/pull/370, and is slightly modified (all import statements have file extensions added to them).
|
||||
|
||||
The `fromFile` functionality has also been removed, as it was not being used.
|
25
packages/astro/src/assets/utils/vendor/image-size/detector.ts
vendored
Normal file
25
packages/astro/src/assets/utils/vendor/image-size/detector.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
import type { imageType } from './types/index.js'
|
||||
import { typeHandlers, types } from './types/index.js'
|
||||
|
||||
// This map helps avoid validating for every single image type
|
||||
const firstBytes = new Map<number, imageType>([
|
||||
[0x38, 'psd'],
|
||||
[0x42, 'bmp'],
|
||||
[0x44, 'dds'],
|
||||
[0x47, 'gif'],
|
||||
[0x49, 'tiff'],
|
||||
[0x4d, 'tiff'],
|
||||
[0x52, 'webp'],
|
||||
[0x69, 'icns'],
|
||||
[0x89, 'png'],
|
||||
[0xff, 'jpg'],
|
||||
])
|
||||
|
||||
export function detector(input: Uint8Array): imageType | undefined {
|
||||
const byte = input[0]
|
||||
const type = firstBytes.get(byte)
|
||||
if (type && typeHandlers.get(type)!.validate(input)) {
|
||||
return type
|
||||
}
|
||||
return types.find((fileType) => typeHandlers.get(fileType)!.validate(input))
|
||||
}
|
43
packages/astro/src/assets/utils/vendor/image-size/lookup.ts
vendored
Normal file
43
packages/astro/src/assets/utils/vendor/image-size/lookup.ts
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
import type { imageType } from './types/index.js'
|
||||
import { typeHandlers } from './types/index.js'
|
||||
import { detector } from './detector.js'
|
||||
import type { ISizeCalculationResult } from './types/interface.ts'
|
||||
|
||||
type Options = {
|
||||
disabledTypes: imageType[]
|
||||
}
|
||||
|
||||
const globalOptions: Options = {
|
||||
disabledTypes: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size information based on an Uint8Array
|
||||
*
|
||||
* @param {Uint8Array} input
|
||||
* @returns {ISizeCalculationResult}
|
||||
*/
|
||||
export function lookup(input: Uint8Array): ISizeCalculationResult {
|
||||
// detect the file type... don't rely on the extension
|
||||
const type = detector(input)
|
||||
|
||||
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
|
||||
const size = typeHandlers.get(type)!.calculate(input)
|
||||
if (size !== undefined) {
|
||||
size.type = size.type ?? type
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// throw up, if we don't understand the file
|
||||
throw new TypeError('unsupported file type: ' + type)
|
||||
}
|
||||
|
||||
export const disableTypes = (types: imageType[]): void => {
|
||||
globalOptions.disabledTypes = types
|
||||
}
|
11
packages/astro/src/assets/utils/vendor/image-size/types/bmp.ts
vendored
Normal file
11
packages/astro/src/assets/utils/vendor/image-size/types/bmp.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toUTF8String, readInt32LE, readUInt32LE } from './utils.js'
|
||||
|
||||
export const BMP: IImage = {
|
||||
validate: (input) => toUTF8String(input, 0, 2) === 'BM',
|
||||
|
||||
calculate: (input) => ({
|
||||
height: Math.abs(readInt32LE(input, 22)),
|
||||
width: readUInt32LE(input, 18),
|
||||
}),
|
||||
}
|
17
packages/astro/src/assets/utils/vendor/image-size/types/cur.ts
vendored
Normal file
17
packages/astro/src/assets/utils/vendor/image-size/types/cur.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { ICO } from './ico.js'
|
||||
import { readUInt16LE } from './utils.js'
|
||||
|
||||
const TYPE_CURSOR = 2
|
||||
export const CUR: IImage = {
|
||||
validate(input) {
|
||||
const reserved = readUInt16LE(input, 0)
|
||||
const imageCount = readUInt16LE(input, 4)
|
||||
if (reserved !== 0 || imageCount === 0) return false
|
||||
|
||||
const imageType = readUInt16LE(input, 2)
|
||||
return imageType === TYPE_CURSOR
|
||||
},
|
||||
|
||||
calculate: (input) => ICO.calculate(input),
|
||||
}
|
11
packages/astro/src/assets/utils/vendor/image-size/types/dds.ts
vendored
Normal file
11
packages/astro/src/assets/utils/vendor/image-size/types/dds.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { readUInt32LE } from './utils.js'
|
||||
|
||||
export const DDS: IImage = {
|
||||
validate: (input) => readUInt32LE(input, 0) === 0x20534444,
|
||||
|
||||
calculate: (input) => ({
|
||||
height: readUInt32LE(input, 12),
|
||||
width: readUInt32LE(input, 16),
|
||||
}),
|
||||
}
|
12
packages/astro/src/assets/utils/vendor/image-size/types/gif.ts
vendored
Normal file
12
packages/astro/src/assets/utils/vendor/image-size/types/gif.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toUTF8String, readUInt16LE } from './utils.js'
|
||||
|
||||
const gifRegexp = /^GIF8[79]a/
|
||||
export const GIF: IImage = {
|
||||
validate: (input) => gifRegexp.test(toUTF8String(input, 0, 6)),
|
||||
|
||||
calculate: (input) => ({
|
||||
height: readUInt16LE(input, 8),
|
||||
width: readUInt16LE(input, 6),
|
||||
}),
|
||||
}
|
55
packages/astro/src/assets/utils/vendor/image-size/types/heif.ts
vendored
Normal file
55
packages/astro/src/assets/utils/vendor/image-size/types/heif.ts
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { findBox, readUInt32BE, toUTF8String } from './utils.js'
|
||||
|
||||
const brandMap = {
|
||||
avif: 'avif',
|
||||
mif1: 'heif',
|
||||
msf1: 'heif', // hief-sequence
|
||||
heic: 'heic',
|
||||
heix: 'heic',
|
||||
hevc: 'heic', // heic-sequence
|
||||
hevx: 'heic', // heic-sequence
|
||||
}
|
||||
|
||||
function detectBrands(buffer: Uint8Array, start: number, end: number) {
|
||||
let brandsDetected = {} as Record<keyof typeof brandMap, 1>;
|
||||
for (let i = start; i <= end; i += 4) {
|
||||
const brand = toUTF8String(buffer, i, i + 4);
|
||||
if (brand in brandMap) {
|
||||
brandsDetected[brand as keyof typeof brandMap] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the most relevant type based on detected brands
|
||||
if ('avif' in brandsDetected) {
|
||||
return 'avif';
|
||||
} else if ('heic' in brandsDetected || 'heix' in brandsDetected || 'hevc' in brandsDetected || 'hevx' in brandsDetected) {
|
||||
return 'heic';
|
||||
} else if ('mif1' in brandsDetected || 'msf1' in brandsDetected) {
|
||||
return 'heif';
|
||||
}
|
||||
}
|
||||
|
||||
export const HEIF: IImage = {
|
||||
validate(buffer) {
|
||||
const ftype = toUTF8String(buffer, 4, 8)
|
||||
const brand = toUTF8String(buffer, 8, 12)
|
||||
return 'ftyp' === ftype && brand in brandMap
|
||||
},
|
||||
|
||||
calculate(buffer) {
|
||||
// Based on https://nokiatech.github.io/heif/technical.html
|
||||
const metaBox = findBox(buffer, 'meta', 0)
|
||||
const iprpBox = metaBox && findBox(buffer, 'iprp', metaBox.offset + 12)
|
||||
const ipcoBox = iprpBox && findBox(buffer, 'ipco', iprpBox.offset + 8)
|
||||
const ispeBox = ipcoBox && findBox(buffer, 'ispe', ipcoBox.offset + 8)
|
||||
if (ispeBox) {
|
||||
return {
|
||||
height: readUInt32BE(buffer, ispeBox.offset + 16),
|
||||
width: readUInt32BE(buffer, ispeBox.offset + 12),
|
||||
type: detectBrands(buffer, 8, metaBox.offset),
|
||||
}
|
||||
}
|
||||
throw new TypeError('Invalid HEIF, no size found')
|
||||
}
|
||||
}
|
113
packages/astro/src/assets/utils/vendor/image-size/types/icns.ts
vendored
Normal file
113
packages/astro/src/assets/utils/vendor/image-size/types/icns.ts
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
import type { IImage, ISize } from './interface.ts'
|
||||
import { toUTF8String, readUInt32BE } from './utils.js'
|
||||
|
||||
/**
|
||||
* 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: { [key: string]: number } = {
|
||||
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(
|
||||
input: Uint8Array,
|
||||
imageOffset: number,
|
||||
): [string, number] {
|
||||
const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET
|
||||
return [
|
||||
toUTF8String(input, imageOffset, imageLengthOffset),
|
||||
readUInt32BE(input, imageLengthOffset),
|
||||
]
|
||||
}
|
||||
|
||||
function getImageSize(type: string): ISize {
|
||||
const size = ICON_TYPE_SIZE[type]
|
||||
return { width: size, height: size, type }
|
||||
}
|
||||
|
||||
export const ICNS: IImage = {
|
||||
validate: (input) => toUTF8String(input, 0, 4) === 'icns',
|
||||
|
||||
calculate(input) {
|
||||
const inputLength = input.length
|
||||
const fileLength = readUInt32BE(input, FILE_LENGTH_OFFSET)
|
||||
let imageOffset = SIZE_HEADER
|
||||
|
||||
let imageHeader = readImageHeader(input, imageOffset)
|
||||
let imageSize = getImageSize(imageHeader[0])
|
||||
imageOffset += imageHeader[1]
|
||||
|
||||
if (imageOffset === fileLength) return imageSize
|
||||
|
||||
const result = {
|
||||
height: imageSize.height,
|
||||
images: [imageSize],
|
||||
width: imageSize.width,
|
||||
}
|
||||
|
||||
while (imageOffset < fileLength && imageOffset < inputLength) {
|
||||
imageHeader = readImageHeader(input, imageOffset)
|
||||
imageSize = getImageSize(imageHeader[0])
|
||||
imageOffset += imageHeader[1]
|
||||
result.images.push(imageSize)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
}
|
75
packages/astro/src/assets/utils/vendor/image-size/types/ico.ts
vendored
Normal file
75
packages/astro/src/assets/utils/vendor/image-size/types/ico.ts
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
import type { IImage, ISize } from './interface.ts'
|
||||
import { readUInt16LE } from './utils.js'
|
||||
|
||||
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 = 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(input: Uint8Array, offset: number): number {
|
||||
const value = input[offset]
|
||||
return value === 0 ? 256 : value
|
||||
}
|
||||
|
||||
function getImageSize(input: Uint8Array, imageIndex: number): ISize {
|
||||
const offset = SIZE_HEADER + imageIndex * SIZE_IMAGE_ENTRY
|
||||
return {
|
||||
height: getSizeFromOffset(input, offset + 1),
|
||||
width: getSizeFromOffset(input, offset),
|
||||
}
|
||||
}
|
||||
|
||||
export const ICO: IImage = {
|
||||
validate(input) {
|
||||
const reserved = readUInt16LE(input, 0)
|
||||
const imageCount = readUInt16LE(input, 4)
|
||||
if (reserved !== 0 || imageCount === 0) return false
|
||||
|
||||
const imageType = readUInt16LE(input, 2)
|
||||
return imageType === TYPE_ICON
|
||||
},
|
||||
|
||||
calculate(input) {
|
||||
const nbImages = readUInt16LE(input, 4)
|
||||
const imageSize = getImageSize(input, 0)
|
||||
|
||||
if (nbImages === 1) return imageSize
|
||||
|
||||
const imgs: ISize[] = [imageSize]
|
||||
for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) {
|
||||
imgs.push(getImageSize(input, imageIndex))
|
||||
}
|
||||
|
||||
return {
|
||||
height: imageSize.height,
|
||||
images: imgs,
|
||||
width: imageSize.width,
|
||||
}
|
||||
},
|
||||
}
|
44
packages/astro/src/assets/utils/vendor/image-size/types/index.ts
vendored
Normal file
44
packages/astro/src/assets/utils/vendor/image-size/types/index.ts
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
// load all available handlers explicitly for browserify support
|
||||
import { BMP } from './bmp.js'
|
||||
import { CUR } from './cur.js'
|
||||
import { DDS } from './dds.js'
|
||||
import { GIF } from './gif.js'
|
||||
import { HEIF } from './heif.js'
|
||||
import { ICNS } from './icns.js'
|
||||
import { ICO } from './ico.js'
|
||||
import { J2C } from './j2c.js'
|
||||
import { JP2 } from './jp2.js'
|
||||
import { JPG } from './jpg.js'
|
||||
import { KTX } from './ktx.js'
|
||||
import { PNG } from './png.js'
|
||||
import { PNM } from './pnm.js'
|
||||
import { PSD } from './psd.js'
|
||||
import { SVG } from './svg.js'
|
||||
import { TGA } from './tga.js'
|
||||
import { TIFF } from './tiff.js'
|
||||
import { WEBP } from './webp.js'
|
||||
|
||||
export const typeHandlers = new Map([
|
||||
['bmp', BMP],
|
||||
['cur', CUR],
|
||||
['dds', DDS],
|
||||
['gif', GIF],
|
||||
['heif', HEIF],
|
||||
['icns', ICNS],
|
||||
['ico', ICO],
|
||||
['j2c', J2C],
|
||||
['jp2', JP2],
|
||||
['jpg', JPG],
|
||||
['ktx', KTX],
|
||||
['png', PNG],
|
||||
['pnm', PNM],
|
||||
['psd', PSD],
|
||||
['svg', SVG],
|
||||
['tga', TGA],
|
||||
['tiff', TIFF],
|
||||
['webp', WEBP],
|
||||
] as const)
|
||||
|
||||
|
||||
export const types = Array.from(typeHandlers.keys())
|
||||
export type imageType = typeof types[number]
|
15
packages/astro/src/assets/utils/vendor/image-size/types/interface.ts
vendored
Normal file
15
packages/astro/src/assets/utils/vendor/image-size/types/interface.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
export type ISize = {
|
||||
width: number | undefined
|
||||
height: number | undefined
|
||||
orientation?: number
|
||||
type?: string
|
||||
}
|
||||
|
||||
export type ISizeCalculationResult = {
|
||||
images?: ISize[]
|
||||
} & ISize
|
||||
|
||||
export type IImage = {
|
||||
validate: (input: Uint8Array) => boolean
|
||||
calculate: (input: Uint8Array) => ISizeCalculationResult
|
||||
}
|
12
packages/astro/src/assets/utils/vendor/image-size/types/j2c.ts
vendored
Normal file
12
packages/astro/src/assets/utils/vendor/image-size/types/j2c.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toHexString, readUInt32BE } from './utils.js'
|
||||
|
||||
export const J2C: IImage = {
|
||||
// TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC
|
||||
validate: (input) => toHexString(input, 0, 4) === 'ff4fff51',
|
||||
|
||||
calculate: (input) => ({
|
||||
height: readUInt32BE(input, 12),
|
||||
width: readUInt32BE(input, 8),
|
||||
}),
|
||||
}
|
23
packages/astro/src/assets/utils/vendor/image-size/types/jp2.ts
vendored
Normal file
23
packages/astro/src/assets/utils/vendor/image-size/types/jp2.ts
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { readUInt32BE, findBox } from './utils.js'
|
||||
|
||||
export const JP2: IImage = {
|
||||
validate(input) {
|
||||
if (readUInt32BE(input, 4) !== 0x6a502020 || readUInt32BE(input, 0) < 1) return false
|
||||
const ftypBox = findBox(input, 'ftyp', 0)
|
||||
if (!ftypBox) return false
|
||||
return readUInt32BE(input, ftypBox.offset + 4) === 0x66747970
|
||||
},
|
||||
|
||||
calculate(input) {
|
||||
const jp2hBox = findBox(input, 'jp2h', 0)
|
||||
const ihdrBox = jp2hBox && findBox(input, 'ihdr', jp2hBox.offset + 8)
|
||||
if (ihdrBox) {
|
||||
return {
|
||||
height: readUInt32BE(input, ihdrBox.offset + 8),
|
||||
width: readUInt32BE(input, ihdrBox.offset + 12),
|
||||
}
|
||||
}
|
||||
throw new TypeError('Unsupported JPEG 2000 format')
|
||||
},
|
||||
}
|
161
packages/astro/src/assets/utils/vendor/image-size/types/jpg.ts
vendored
Normal file
161
packages/astro/src/assets/utils/vendor/image-size/types/jpg.ts
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// 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.
|
||||
|
||||
import type { IImage, ISize } from './interface.ts'
|
||||
import { readUInt, readUInt16BE, toHexString } from './utils.js'
|
||||
|
||||
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(input: Uint8Array): boolean {
|
||||
return toHexString(input, 2, 6) === EXIF_MARKER
|
||||
}
|
||||
|
||||
function extractSize(input: Uint8Array, index: number): ISize {
|
||||
return {
|
||||
height: readUInt16BE(input, index),
|
||||
width: readUInt16BE(input, index + 2),
|
||||
}
|
||||
}
|
||||
|
||||
function extractOrientation(exifBlock: Uint8Array, isBigEndian: boolean) {
|
||||
// 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(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(block, 16, 0, isBigEndian)
|
||||
|
||||
// 0x0112 (decimal: 274) is the `orientation` tag ID
|
||||
if (tagNumber === 274) {
|
||||
const dataFormat = 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(block, 32, 4, isBigEndian)
|
||||
if (numberOfComponents !== 1) {
|
||||
return
|
||||
}
|
||||
|
||||
return readUInt(block, 16, 8, isBigEndian)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateExifBlock(input: Uint8Array, index: number) {
|
||||
// Skip APP1 Data Size
|
||||
const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index)
|
||||
|
||||
// Consider byte alignment
|
||||
const byteAlign = toHexString(
|
||||
exifBlock,
|
||||
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 validateInput(input: Uint8Array, index: number): void {
|
||||
// index should be within buffer limits
|
||||
if (index > input.length) {
|
||||
throw new TypeError('Corrupt JPG, exceeded buffer limits')
|
||||
}
|
||||
}
|
||||
|
||||
export const JPG: IImage = {
|
||||
validate: (input) => toHexString(input, 0, 2) === 'ffd8',
|
||||
|
||||
calculate(input) {
|
||||
// Skip 4 chars, they are for signature
|
||||
input = input.slice(4)
|
||||
|
||||
let orientation: number | undefined
|
||||
let next: number
|
||||
while (input.length) {
|
||||
// read length of the next block
|
||||
const i = readUInt16BE(input, 0)
|
||||
|
||||
// Every JPEG block must begin with a 0xFF
|
||||
if (input[i] !== 0xff) {
|
||||
input = input.slice(1)
|
||||
continue
|
||||
}
|
||||
|
||||
if (isEXIF(input)) {
|
||||
orientation = validateExifBlock(input, i)
|
||||
}
|
||||
|
||||
// ensure correct format
|
||||
validateInput(input, i)
|
||||
|
||||
// 0xFFC0 is baseline standard(SOF)
|
||||
// 0xFFC1 is baseline optimized(SOF)
|
||||
// 0xFFC2 is progressive(SOF2)
|
||||
next = input[i + 1]
|
||||
if (next === 0xc0 || next === 0xc1 || next === 0xc2) {
|
||||
const size = extractSize(input, 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
|
||||
input = input.slice(i + 2)
|
||||
}
|
||||
|
||||
throw new TypeError('Invalid JPG, no size found')
|
||||
},
|
||||
}
|
19
packages/astro/src/assets/utils/vendor/image-size/types/ktx.ts
vendored
Normal file
19
packages/astro/src/assets/utils/vendor/image-size/types/ktx.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toUTF8String, readUInt32LE } from './utils.js'
|
||||
|
||||
export const KTX: IImage = {
|
||||
validate: (input) => {
|
||||
const signature = toUTF8String(input, 1, 7)
|
||||
return ['KTX 11', 'KTX 20'].includes(signature)
|
||||
},
|
||||
|
||||
calculate: (input) => {
|
||||
const type = input[5] === 0x31 ? 'ktx' : 'ktx2'
|
||||
const offset = type === 'ktx' ? 36 : 20
|
||||
return ({
|
||||
height: readUInt32LE(input, offset + 4),
|
||||
width: readUInt32LE(input, offset),
|
||||
type,
|
||||
})
|
||||
},
|
||||
}
|
37
packages/astro/src/assets/utils/vendor/image-size/types/png.ts
vendored
Normal file
37
packages/astro/src/assets/utils/vendor/image-size/types/png.ts
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toUTF8String, readUInt32BE } from './utils.js'
|
||||
|
||||
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'
|
||||
|
||||
export const PNG: IImage = {
|
||||
validate(input) {
|
||||
if (pngSignature === toUTF8String(input, 1, 8)) {
|
||||
let chunkName = toUTF8String(input, 12, 16)
|
||||
if (chunkName === pngFriedChunkName) {
|
||||
chunkName = toUTF8String(input, 28, 32)
|
||||
}
|
||||
if (chunkName !== pngImageHeaderChunkName) {
|
||||
throw new TypeError('Invalid PNG')
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
calculate(input) {
|
||||
if (toUTF8String(input, 12, 16) === pngFriedChunkName) {
|
||||
return {
|
||||
height: readUInt32BE(input, 36),
|
||||
width: readUInt32BE(input, 32),
|
||||
}
|
||||
}
|
||||
return {
|
||||
height: readUInt32BE(input, 20),
|
||||
width: readUInt32BE(input, 16),
|
||||
}
|
||||
},
|
||||
}
|
80
packages/astro/src/assets/utils/vendor/image-size/types/pnm.ts
vendored
Normal file
80
packages/astro/src/assets/utils/vendor/image-size/types/pnm.ts
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
import type { IImage, ISize } from './interface.ts'
|
||||
import { toUTF8String } from './utils.js'
|
||||
|
||||
const PNMTypes = {
|
||||
P1: 'pbm/ascii',
|
||||
P2: 'pgm/ascii',
|
||||
P3: 'ppm/ascii',
|
||||
P4: 'pbm',
|
||||
P5: 'pgm',
|
||||
P6: 'ppm',
|
||||
P7: 'pam',
|
||||
PF: 'pfm',
|
||||
} as const
|
||||
|
||||
type ValidSignature = keyof typeof PNMTypes
|
||||
type Handler = (type: string[]) => ISize
|
||||
|
||||
const handlers: { [type: string]: Handler } = {
|
||||
default: (lines) => {
|
||||
let dimensions: string[] = []
|
||||
|
||||
while (lines.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
||||
const line = lines.shift() as string
|
||||
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: { [key: string]: number } = {}
|
||||
while (lines.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
||||
const line = lines.shift() as string
|
||||
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')
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const PNM: IImage = {
|
||||
validate: (input) => toUTF8String(input, 0, 2) in PNMTypes,
|
||||
|
||||
calculate(input) {
|
||||
const signature = toUTF8String(input, 0, 2) as ValidSignature
|
||||
const type = PNMTypes[signature]
|
||||
// TODO: this probably generates garbage. move to a stream based parser
|
||||
const lines = toUTF8String(input, 3).split(/[\r\n]+/)
|
||||
const handler = handlers[type] || handlers.default
|
||||
return handler(lines)
|
||||
},
|
||||
}
|
11
packages/astro/src/assets/utils/vendor/image-size/types/psd.ts
vendored
Normal file
11
packages/astro/src/assets/utils/vendor/image-size/types/psd.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { toUTF8String, readUInt32BE } from './utils.js'
|
||||
|
||||
export const PSD: IImage = {
|
||||
validate: (input) => toUTF8String(input, 0, 4) === '8BPS',
|
||||
|
||||
calculate: (input) => ({
|
||||
height: readUInt32BE(input, 14),
|
||||
width: readUInt32BE(input, 18),
|
||||
}),
|
||||
}
|
110
packages/astro/src/assets/utils/vendor/image-size/types/svg.ts
vendored
Normal file
110
packages/astro/src/assets/utils/vendor/image-size/types/svg.ts
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
import type { IImage, ISize } from './interface.ts'
|
||||
import { toUTF8String } from './utils.js'
|
||||
|
||||
type IAttributes = {
|
||||
width: number | null
|
||||
height: number | null
|
||||
viewbox?: IAttributes | null
|
||||
}
|
||||
|
||||
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: { [unit: string]: number } = {
|
||||
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(
|
||||
// eslint-disable-next-line regexp/prefer-d
|
||||
`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join('|')})?$`,
|
||||
)
|
||||
|
||||
function parseLength(len: string) {
|
||||
const m = unitsReg.exec(len)
|
||||
if (!m) {
|
||||
return undefined
|
||||
}
|
||||
return Math.round(Number(m[1]) * (units[m[2]] || 1))
|
||||
}
|
||||
|
||||
function parseViewbox(viewbox: string): IAttributes {
|
||||
const bounds = viewbox.split(' ')
|
||||
return {
|
||||
height: parseLength(bounds[3]) as number,
|
||||
width: parseLength(bounds[2]) as number,
|
||||
}
|
||||
}
|
||||
|
||||
function parseAttributes(root: string): IAttributes {
|
||||
const width = root.match(extractorRegExps.width)
|
||||
const height = root.match(extractorRegExps.height)
|
||||
const viewbox = root.match(extractorRegExps.viewbox)
|
||||
return {
|
||||
height: height && (parseLength(height[2]) as number),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
viewbox: viewbox && (parseViewbox(viewbox[2]) as IAttributes),
|
||||
width: width && (parseLength(width[2]) as number),
|
||||
}
|
||||
}
|
||||
|
||||
function calculateByDimensions(attrs: IAttributes): ISize {
|
||||
return {
|
||||
height: attrs.height as number,
|
||||
width: attrs.width as number,
|
||||
}
|
||||
}
|
||||
|
||||
function calculateByViewbox(attrs: IAttributes, viewbox: IAttributes): ISize {
|
||||
const ratio = (viewbox.width as number) / (viewbox.height as number)
|
||||
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 as number,
|
||||
width: viewbox.width as number,
|
||||
}
|
||||
}
|
||||
|
||||
export const SVG: IImage = {
|
||||
// Scan only the first kilo-byte to speed up the check on larger files
|
||||
validate: (input) => svgReg.test(toUTF8String(input, 0, 1000)),
|
||||
|
||||
calculate(input) {
|
||||
const root = toUTF8String(input).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')
|
||||
},
|
||||
}
|
15
packages/astro/src/assets/utils/vendor/image-size/types/tga.ts
vendored
Normal file
15
packages/astro/src/assets/utils/vendor/image-size/types/tga.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
import type { IImage } from './interface.ts'
|
||||
import { readUInt16LE } from './utils.js'
|
||||
|
||||
export const TGA: IImage = {
|
||||
validate(input) {
|
||||
return readUInt16LE(input, 0) === 0 && readUInt16LE(input, 4) === 0
|
||||
},
|
||||
|
||||
calculate(input) {
|
||||
return {
|
||||
height: readUInt16LE(input, 14),
|
||||
width: readUInt16LE(input, 12),
|
||||
}
|
||||
},
|
||||
}
|
93
packages/astro/src/assets/utils/vendor/image-size/types/tiff.ts
vendored
Normal file
93
packages/astro/src/assets/utils/vendor/image-size/types/tiff.ts
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
// based on http://www.compix.com/fileformattif.htm
|
||||
// TO-DO: support big-endian as well
|
||||
import type { IImage } from './interface.ts'
|
||||
import { readUInt, toHexString, toUTF8String } from './utils.js'
|
||||
|
||||
// Read IFD (image-file-directory) into a buffer
|
||||
function readIFD(input: Uint8Array, isBigEndian: boolean) {
|
||||
const ifdOffset = readUInt(input, 32, 4, isBigEndian)
|
||||
return input.slice(ifdOffset + 2)
|
||||
}
|
||||
|
||||
// TIFF values seem to be messed up on Big-Endian, this helps
|
||||
function readValue(input: Uint8Array, isBigEndian: boolean): number {
|
||||
const low = readUInt(input, 16, 8, isBigEndian)
|
||||
const high = readUInt(input, 16, 10, isBigEndian)
|
||||
return (high << 16) + low
|
||||
}
|
||||
|
||||
// move to the next tag
|
||||
function nextTag(input: Uint8Array) {
|
||||
if (input.length > 24) {
|
||||
return input.slice(12)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract IFD tags from TIFF metadata
|
||||
function extractTags(input: Uint8Array, isBigEndian: boolean) {
|
||||
const tags: { [key: number]: number } = {}
|
||||
|
||||
let temp: Uint8Array | undefined = input
|
||||
while (temp && temp.length) {
|
||||
const code = readUInt(temp, 16, 0, isBigEndian)
|
||||
const type = readUInt(temp, 16, 2, isBigEndian)
|
||||
const length = 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(input: Uint8Array) {
|
||||
const signature = toUTF8String(input, 0, 2)
|
||||
if ('II' === signature) {
|
||||
return 'LE'
|
||||
} else if ('MM' === signature) {
|
||||
return 'BE'
|
||||
}
|
||||
}
|
||||
|
||||
const signatures = [
|
||||
// '492049', // currently not supported
|
||||
'49492a00', // Little endian
|
||||
'4d4d002a', // Big Endian
|
||||
// '4d4d002a', // BigTIFF > 4GB. currently not supported
|
||||
]
|
||||
|
||||
export const TIFF: IImage = {
|
||||
validate: (input) => signatures.includes(toHexString(input, 0, 4)),
|
||||
|
||||
calculate(input) {
|
||||
// Determine BE/LE
|
||||
const isBigEndian = determineEndianness(input) === 'BE'
|
||||
|
||||
// read the IFD
|
||||
const ifdBuffer = readIFD(input, 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 }
|
||||
},
|
||||
}
|
84
packages/astro/src/assets/utils/vendor/image-size/types/utils.ts
vendored
Normal file
84
packages/astro/src/assets/utils/vendor/image-size/types/utils.ts
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
const decoder = new TextDecoder()
|
||||
export const toUTF8String = (
|
||||
input: Uint8Array,
|
||||
start = 0,
|
||||
end = input.length,
|
||||
) => decoder.decode(input.slice(start, end))
|
||||
|
||||
export const toHexString = (input: Uint8Array, start = 0, end = input.length) =>
|
||||
input
|
||||
.slice(start, end)
|
||||
.reduce((memo, i) => memo + ('0' + i.toString(16)).slice(-2), '')
|
||||
|
||||
export const readInt16LE = (input: Uint8Array, offset = 0) => {
|
||||
const val = input[offset] + input[offset + 1] * 2 ** 8
|
||||
return val | ((val & (2 ** 15)) * 0x1fffe)
|
||||
}
|
||||
|
||||
export const readUInt16BE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] * 2 ** 8 + input[offset + 1]
|
||||
|
||||
export const readUInt16LE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] + input[offset + 1] * 2 ** 8
|
||||
|
||||
export const readUInt24LE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16
|
||||
|
||||
export const readInt32LE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] +
|
||||
input[offset + 1] * 2 ** 8 +
|
||||
input[offset + 2] * 2 ** 16 +
|
||||
(input[offset + 3] << 24)
|
||||
|
||||
export const readUInt32BE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] * 2 ** 24 +
|
||||
input[offset + 1] * 2 ** 16 +
|
||||
input[offset + 2] * 2 ** 8 +
|
||||
input[offset + 3]
|
||||
|
||||
export const readUInt32LE = (input: Uint8Array, offset = 0) =>
|
||||
input[offset] +
|
||||
input[offset + 1] * 2 ** 8 +
|
||||
input[offset + 2] * 2 ** 16 +
|
||||
input[offset + 3] * 2 ** 24
|
||||
|
||||
// Abstract reading multi-byte unsigned integers
|
||||
const methods = {
|
||||
readUInt16BE,
|
||||
readUInt16LE,
|
||||
readUInt32BE,
|
||||
readUInt32LE,
|
||||
} as const
|
||||
|
||||
type MethodName = keyof typeof methods
|
||||
export function readUInt(
|
||||
input: Uint8Array,
|
||||
bits: 16 | 32,
|
||||
offset: number,
|
||||
isBigEndian: boolean,
|
||||
): number {
|
||||
offset = offset || 0
|
||||
const endian = isBigEndian ? 'BE' : 'LE'
|
||||
const methodName: MethodName = ('readUInt' + bits + endian) as MethodName
|
||||
return methods[methodName](input, offset)
|
||||
}
|
||||
|
||||
function readBox(buffer: Uint8Array, offset: number) {
|
||||
if (buffer.length - offset < 4) return
|
||||
const boxSize = readUInt32BE(buffer, offset)
|
||||
if (buffer.length - offset < boxSize) return
|
||||
return {
|
||||
name: toUTF8String(buffer, 4 + offset, 8 + offset),
|
||||
offset,
|
||||
size: boxSize,
|
||||
}
|
||||
}
|
||||
|
||||
export function findBox(buffer: Uint8Array, boxName: string, offset: number) {
|
||||
while (offset < buffer.length) {
|
||||
const box = readBox(buffer, offset)
|
||||
if (!box) break
|
||||
if (box.name === boxName) return box
|
||||
offset += box.size
|
||||
}
|
||||
}
|
68
packages/astro/src/assets/utils/vendor/image-size/types/webp.ts
vendored
Normal file
68
packages/astro/src/assets/utils/vendor/image-size/types/webp.ts
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
// based on https://developers.google.com/speed/webp/docs/riff_container
|
||||
import type { IImage, ISize } from './interface.ts'
|
||||
import { toHexString, toUTF8String, readInt16LE, readUInt24LE } from './utils.js'
|
||||
|
||||
function calculateExtended(input: Uint8Array): ISize {
|
||||
return {
|
||||
height: 1 + readUInt24LE(input, 7),
|
||||
width: 1 + readUInt24LE(input, 4),
|
||||
}
|
||||
}
|
||||
|
||||
function calculateLossless(input: Uint8Array): ISize {
|
||||
return {
|
||||
height:
|
||||
1 +
|
||||
(((input[4] & 0xf) << 10) | (input[3] << 2) | ((input[2] & 0xc0) >> 6)),
|
||||
width: 1 + (((input[2] & 0x3f) << 8) | input[1]),
|
||||
}
|
||||
}
|
||||
|
||||
function calculateLossy(input: Uint8Array): ISize {
|
||||
// `& 0x3fff` returns the last 14 bits
|
||||
// TO-DO: include webp scaling in the calculations
|
||||
return {
|
||||
height: readInt16LE(input, 8) & 0x3fff,
|
||||
width: readInt16LE(input, 6) & 0x3fff,
|
||||
}
|
||||
}
|
||||
|
||||
export const WEBP: IImage = {
|
||||
validate(input) {
|
||||
const riffHeader = 'RIFF' === toUTF8String(input, 0, 4)
|
||||
const webpHeader = 'WEBP' === toUTF8String(input, 8, 12)
|
||||
const vp8Header = 'VP8' === toUTF8String(input, 12, 15)
|
||||
return riffHeader && webpHeader && vp8Header
|
||||
},
|
||||
|
||||
calculate(input) {
|
||||
const chunkHeader = toUTF8String(input, 12, 16)
|
||||
input = input.slice(20, 30)
|
||||
|
||||
// Extended webp stream signature
|
||||
if (chunkHeader === 'VP8X') {
|
||||
const extendedHeader = input[0]
|
||||
const validStart = (extendedHeader & 0xc0) === 0
|
||||
const validEnd = (extendedHeader & 0x01) === 0
|
||||
if (validStart && validEnd) {
|
||||
return calculateExtended(input)
|
||||
} else {
|
||||
// TODO: breaking change
|
||||
throw new TypeError('Invalid WebP')
|
||||
}
|
||||
}
|
||||
|
||||
// Lossless webp stream signature
|
||||
if (chunkHeader === 'VP8 ' && input[0] !== 0x2f) {
|
||||
return calculateLossy(input)
|
||||
}
|
||||
|
||||
// Lossy webp stream signature
|
||||
const signature = toHexString(input, 3, 6)
|
||||
if (chunkHeader === 'VP8L' && signature !== '9d012a') {
|
||||
return calculateLossless(input)
|
||||
}
|
||||
|
||||
throw new TypeError('Invalid WebP')
|
||||
},
|
||||
}
|
|
@ -515,12 +515,12 @@ export const InvalidImageService = {
|
|||
/**
|
||||
* @docs
|
||||
* @message
|
||||
* Missing width and height attributes for `IMAGE_URL`. When using remote images, both dimensions are always required in order to avoid cumulative layout shift (CLS).
|
||||
* Missing width and height attributes for `IMAGE_URL`. When using remote images, both dimensions are required in order to avoid cumulative layout shift (CLS).
|
||||
* @see
|
||||
* - [Images](https://docs.astro.build/en/guides/images/)
|
||||
* - [Image component#width-and-height-required](https://docs.astro.build/en/guides/images/#width-and-height-required-for-public-and-remote-images)
|
||||
* - [Image component#width-and-height-required](https://docs.astro.build/en/guides/images/#width-and-height-required-for-images-in-public)
|
||||
* @description
|
||||
* For remote images, `width` and `height` cannot be inferred from the original file. As such, in order to avoid CLS, those two properties are always required.
|
||||
* For remote images, `width` and `height` cannot automatically be inferred from the original file. To avoid cumulative layout shift (CLS), either specify these two properties, or set [inferSize`](https://docs.astro.build/en/guides/images/#infersize) to `true` to fetch a remote image's original dimensions.
|
||||
*
|
||||
* If your image is inside your `src` folder, you probably meant to import it instead. See [the Imports guide for more information](https://docs.astro.build/en/guides/imports/#other-assets).
|
||||
*/
|
||||
|
@ -530,8 +530,22 @@ export const MissingImageDimension = {
|
|||
message: (missingDimension: 'width' | 'height' | 'both', imageURL: string) =>
|
||||
`Missing ${
|
||||
missingDimension === 'both' ? 'width and height attributes' : `${missingDimension} attribute`
|
||||
} for ${imageURL}. When using remote images, both dimensions are always required in order to avoid CLS.`,
|
||||
hint: 'If your image is inside your `src` folder, you probably meant to import it instead. See [the Imports guide for more information](https://docs.astro.build/en/guides/imports/#other-assets).',
|
||||
} for ${imageURL}. When using remote images, both dimensions are required unless in order to avoid CLS.`,
|
||||
hint: 'If your image is inside your `src` folder, you probably meant to import it instead. See [the Imports guide for more information](https://docs.astro.build/en/guides/imports/#other-assets). You can also use `inferSize={true}` for remote images to get the original dimensions.',
|
||||
} satisfies ErrorData;
|
||||
/**
|
||||
* @docs
|
||||
* @message
|
||||
* Failed to get the dimensions for `IMAGE_URL`.
|
||||
* @description
|
||||
* Determining the remote image's dimensions failed. This is typically caused by an incorrect URL or attempting to infer the size of an image in the public folder which is not possible. Here is where the probing logic lives in Astro's [sourcecode](https://github.com/withastro/astro/blob/main/packages/astro/src/assets/utils/image-size/).
|
||||
*/
|
||||
export const FailedToFetchRemoteImageDimensions = {
|
||||
name: 'FailedToFetchRemoteImageDimensions',
|
||||
title: 'Failed to retrieve remote image dimensions',
|
||||
message: (imageURL: string) =>
|
||||
`Failed to get the dimensions for ${imageURL}.`,
|
||||
hint: 'Verify your remote image URL is accurate, and that you are not using `inferSize` with a file located in your `public/` folder.',
|
||||
} satisfies ErrorData;
|
||||
/**
|
||||
* @docs
|
||||
|
|
71
packages/astro/test/core-image-infersize.test.js
Normal file
71
packages/astro/test/core-image-infersize.test.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { Writable } from 'node:stream';
|
||||
|
||||
import { Logger } from '../dist/core/logger/core.js';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('astro:image:infersize', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
describe('dev', () => {
|
||||
/** @type {import('./test-utils').DevServer} */
|
||||
let devServer;
|
||||
/** @type {Array<{ type: any, level: 'error', message: string; }>} */
|
||||
let logs = [];
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/core-image-infersize/',
|
||||
});
|
||||
|
||||
devServer = await fixture.startDevServer({
|
||||
logger: new Logger({
|
||||
level: 'error',
|
||||
dest: new Writable({
|
||||
objectMode: true,
|
||||
write(event, _, callback) {
|
||||
logs.push(event);
|
||||
callback();
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
describe('inferSize works', () => {
|
||||
let $;
|
||||
before(async () => {
|
||||
let res = await fixture.fetch('/');
|
||||
let html = await res.text();
|
||||
$ = cheerio.load(html);
|
||||
});
|
||||
|
||||
it('Image component works', async () => {
|
||||
let $img = $('img');
|
||||
expect(
|
||||
$img.attr('src').startsWith('/_image') && $img.attr('src').endsWith('f=webp')
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('Picture component works', async () => {
|
||||
let $img = $('picture img');
|
||||
expect(
|
||||
$img.attr('src').startsWith('/_image') && $img.attr('src').endsWith('f=png')
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('getImage works', async () => {
|
||||
let $img = $('#getImage');
|
||||
expect(
|
||||
$img.attr('src').startsWith('/_image') && $img.attr('src').endsWith('f=webp')
|
||||
).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
8
packages/astro/test/fixtures/core-image-infersize/astro.config.mjs
vendored
Normal file
8
packages/astro/test/fixtures/core-image-infersize/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
image: {
|
||||
domains: ['avatars.githubusercontent.com'],
|
||||
},
|
||||
});
|
11
packages/astro/test/fixtures/core-image-infersize/package.json
vendored
Normal file
11
packages/astro/test/fixtures/core-image-infersize/package.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "@test/core-image-remark-infersize",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev"
|
||||
}
|
||||
}
|
12
packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro
vendored
Normal file
12
packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
// https://avatars.githubusercontent.com/u/622227?s=64 is a .jpeg
|
||||
import { Image, Picture, getImage } from 'astro:assets';
|
||||
const remoteImg = await getImage({
|
||||
src: 'https://avatars.githubusercontent.com/u/622227?s=64',
|
||||
inferSize: true,
|
||||
alt: '',
|
||||
});
|
||||
---
|
||||
<Image src="https://avatars.githubusercontent.com/u/622227?s=64," inferSize={true} , alt="" />
|
||||
<Picture src="https://avatars.githubusercontent.com/u/622227?s=64," inferSize={true} , alt="" />
|
||||
<img src={remoteImg.src} {...remoteImg.attributes} id="getImage" />
|
6
packages/astro/test/fixtures/core-image-infersize/tsconfig.json
vendored
Normal file
6
packages/astro/test/fixtures/core-image-infersize/tsconfig.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
}
|
||||
}
|
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
|
@ -638,9 +638,6 @@ importers:
|
|||
preferred-pm:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2
|
||||
probe-image-size:
|
||||
specifier: ^7.2.3
|
||||
version: 7.2.3
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
|
@ -2532,6 +2529,12 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/core-image-infersize:
|
||||
dependencies:
|
||||
astro:
|
||||
specifier: workspace:*
|
||||
version: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/core-image-remark-imgattr:
|
||||
dependencies:
|
||||
astro:
|
||||
|
@ -9161,17 +9164,6 @@ packages:
|
|||
dependencies:
|
||||
ms: 2.0.0
|
||||
|
||||
/debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
dev: false
|
||||
|
||||
/debug@4.3.4(supports-color@8.1.1):
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
@ -10818,6 +10810,7 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: true
|
||||
|
||||
/iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
|
@ -11517,6 +11510,7 @@ packages:
|
|||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.startcase@4.4.0:
|
||||
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
|
||||
|
@ -12515,18 +12509,6 @@ packages:
|
|||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/needle@2.9.1:
|
||||
resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
|
||||
engines: {node: '>= 4.4.x'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
iconv-lite: 0.4.24
|
||||
sax: 1.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/negotiator@0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -13610,16 +13592,6 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/probe-image-size@7.2.3:
|
||||
resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==}
|
||||
dependencies:
|
||||
lodash.merge: 4.6.2
|
||||
needle: 2.9.1
|
||||
stream-parser: 0.3.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
@ -14207,6 +14179,7 @@ packages:
|
|||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: true
|
||||
|
||||
/sass-formatter@0.7.8:
|
||||
resolution: {integrity: sha512-7fI2a8THglflhhYis7k06eUf92VQuJoXzEs2KRP0r1bluFxKFvLx0Ns7c478oYGM0fPfrr846ZRWVi2MAgHt9Q==}
|
||||
|
@ -14622,14 +14595,6 @@ packages:
|
|||
bl: 5.1.0
|
||||
dev: false
|
||||
|
||||
/stream-parser@0.3.1:
|
||||
resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/stream-transform@2.1.3:
|
||||
resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==}
|
||||
dependencies:
|
||||
|
|
Loading…
Add table
Reference in a new issue