1598 lines
47 KiB
JavaScript
1598 lines
47 KiB
JavaScript
import { i as isRemoteAllowed, j as joinPaths, a as isRemotePath, r as removeQueryString, b as isParentDirectory } from './remote_OOD9OFqU.mjs';
|
||
import { A as AstroError, E as ExpectedImage, L as LocalImageUsedWrongly, M as MissingImageDimension, U as UnsupportedImageFormat, I as IncompatibleDescriptorOptions, a as UnsupportedImageConversion, t as toStyleString, N as NoImageMetadata, F as FailedToFetchRemoteImageDimensions, b as ExpectedImageOptions, c as ExpectedNotESMImage, d as InvalidImageService, e as createComponent, f as createAstro, g as ImageMissingAlt, m as maybeRenderHead, h as addAttribute, s as spreadAttributes, r as renderTemplate, i as ExperimentalFontsNotEnabled, j as FontFamilyNotFound, u as unescapeHTML } from './astro/server_BRK6phUk.mjs';
|
||
import 'clsx';
|
||
import * as mime from 'mrmime';
|
||
import 'kleur/colors';
|
||
import { readFile } from 'node:fs/promises';
|
||
import { fileURLToPath } from 'node:url';
|
||
import '../renderers.mjs';
|
||
|
||
const VALID_SUPPORTED_FORMATS = [
|
||
"jpeg",
|
||
"jpg",
|
||
"png",
|
||
"tiff",
|
||
"webp",
|
||
"gif",
|
||
"svg",
|
||
"avif"
|
||
];
|
||
const DEFAULT_OUTPUT_FORMAT = "webp";
|
||
const DEFAULT_HASH_PROPS = [
|
||
"src",
|
||
"width",
|
||
"height",
|
||
"format",
|
||
"quality",
|
||
"fit",
|
||
"position"
|
||
];
|
||
|
||
const DEFAULT_RESOLUTIONS = [
|
||
640,
|
||
// older and lower-end phones
|
||
750,
|
||
// iPhone 6-8
|
||
828,
|
||
// iPhone XR/11
|
||
960,
|
||
// older horizontal phones
|
||
1080,
|
||
// iPhone 6-8 Plus
|
||
1280,
|
||
// 720p
|
||
1668,
|
||
// Various iPads
|
||
1920,
|
||
// 1080p
|
||
2048,
|
||
// QXGA
|
||
2560,
|
||
// WQXGA
|
||
3200,
|
||
// QHD+
|
||
3840,
|
||
// 4K
|
||
4480,
|
||
// 4.5K
|
||
5120,
|
||
// 5K
|
||
6016
|
||
// 6K
|
||
];
|
||
const LIMITED_RESOLUTIONS = [
|
||
640,
|
||
// older and lower-end phones
|
||
750,
|
||
// iPhone 6-8
|
||
828,
|
||
// iPhone XR/11
|
||
1080,
|
||
// iPhone 6-8 Plus
|
||
1280,
|
||
// 720p
|
||
1668,
|
||
// Various iPads
|
||
2048,
|
||
// QXGA
|
||
2560
|
||
// WQXGA
|
||
];
|
||
const getWidths = ({
|
||
width,
|
||
layout,
|
||
breakpoints = DEFAULT_RESOLUTIONS,
|
||
originalWidth
|
||
}) => {
|
||
const smallerThanOriginal = (w) => !originalWidth || w <= originalWidth;
|
||
if (layout === "full-width") {
|
||
return breakpoints.filter(smallerThanOriginal);
|
||
}
|
||
if (!width) {
|
||
return [];
|
||
}
|
||
const doubleWidth = width * 2;
|
||
const maxSize = originalWidth ? Math.min(doubleWidth, originalWidth) : doubleWidth;
|
||
if (layout === "fixed") {
|
||
return originalWidth && width > originalWidth ? [originalWidth] : [width, maxSize];
|
||
}
|
||
if (layout === "constrained") {
|
||
return [
|
||
// Always include the image at 1x and 2x the specified width
|
||
width,
|
||
doubleWidth,
|
||
...breakpoints
|
||
].filter((w) => w <= maxSize).sort((a, b) => a - b);
|
||
}
|
||
return [];
|
||
};
|
||
const getSizesAttribute = ({
|
||
width,
|
||
layout
|
||
}) => {
|
||
if (!width || !layout) {
|
||
return void 0;
|
||
}
|
||
switch (layout) {
|
||
// If screen is wider than the max size then image width is the max size,
|
||
// otherwise it's the width of the screen
|
||
case "constrained":
|
||
return `(min-width: ${width}px) ${width}px, 100vw`;
|
||
// Image is always the same width, whatever the size of the screen
|
||
case "fixed":
|
||
return `${width}px`;
|
||
// Image is always the width of the screen
|
||
case "full-width":
|
||
return `100vw`;
|
||
case "none":
|
||
default:
|
||
return void 0;
|
||
}
|
||
};
|
||
|
||
function isESMImportedImage(src) {
|
||
return typeof src === "object" || typeof src === "function" && "src" in src;
|
||
}
|
||
function isRemoteImage(src) {
|
||
return typeof src === "string";
|
||
}
|
||
async function resolveSrc(src) {
|
||
if (typeof src === "object" && "then" in src) {
|
||
const resource = await src;
|
||
return resource.default ?? resource;
|
||
}
|
||
return src;
|
||
}
|
||
|
||
function isLocalService(service) {
|
||
if (!service) {
|
||
return false;
|
||
}
|
||
return "transform" in service;
|
||
}
|
||
function parseQuality(quality) {
|
||
let result = parseInt(quality);
|
||
if (Number.isNaN(result)) {
|
||
return quality;
|
||
}
|
||
return result;
|
||
}
|
||
const sortNumeric = (a, b) => a - b;
|
||
const baseService = {
|
||
validateOptions(options) {
|
||
if (!options.src || !isRemoteImage(options.src) && !isESMImportedImage(options.src)) {
|
||
throw new AstroError({
|
||
...ExpectedImage,
|
||
message: ExpectedImage.message(
|
||
JSON.stringify(options.src),
|
||
typeof options.src,
|
||
JSON.stringify(options, (_, v) => v === void 0 ? null : v)
|
||
)
|
||
});
|
||
}
|
||
if (!isESMImportedImage(options.src)) {
|
||
if (options.src.startsWith("/@fs/") || !isRemotePath(options.src) && !options.src.startsWith("/")) {
|
||
throw new AstroError({
|
||
...LocalImageUsedWrongly,
|
||
message: LocalImageUsedWrongly.message(options.src)
|
||
});
|
||
}
|
||
let missingDimension;
|
||
if (!options.width && !options.height) {
|
||
missingDimension = "both";
|
||
} else if (!options.width && options.height) {
|
||
missingDimension = "width";
|
||
} else if (options.width && !options.height) {
|
||
missingDimension = "height";
|
||
}
|
||
if (missingDimension) {
|
||
throw new AstroError({
|
||
...MissingImageDimension,
|
||
message: MissingImageDimension.message(missingDimension, options.src)
|
||
});
|
||
}
|
||
} else {
|
||
if (!VALID_SUPPORTED_FORMATS.includes(options.src.format)) {
|
||
throw new AstroError({
|
||
...UnsupportedImageFormat,
|
||
message: UnsupportedImageFormat.message(
|
||
options.src.format,
|
||
options.src.src,
|
||
VALID_SUPPORTED_FORMATS
|
||
)
|
||
});
|
||
}
|
||
if (options.widths && options.densities) {
|
||
throw new AstroError(IncompatibleDescriptorOptions);
|
||
}
|
||
if (options.src.format === "svg") {
|
||
options.format = "svg";
|
||
}
|
||
if (options.src.format === "svg" && options.format !== "svg" || options.src.format !== "svg" && options.format === "svg") {
|
||
throw new AstroError(UnsupportedImageConversion);
|
||
}
|
||
}
|
||
if (!options.format) {
|
||
options.format = DEFAULT_OUTPUT_FORMAT;
|
||
}
|
||
if (options.width) options.width = Math.round(options.width);
|
||
if (options.height) options.height = Math.round(options.height);
|
||
if (options.layout && options.width && options.height) {
|
||
options.fit ??= "cover";
|
||
delete options.layout;
|
||
}
|
||
if (options.fit === "none") {
|
||
delete options.fit;
|
||
}
|
||
return options;
|
||
},
|
||
getHTMLAttributes(options) {
|
||
const { targetWidth, targetHeight } = getTargetDimensions(options);
|
||
const {
|
||
src,
|
||
width,
|
||
height,
|
||
format,
|
||
quality,
|
||
densities,
|
||
widths,
|
||
formats,
|
||
layout,
|
||
priority,
|
||
fit,
|
||
position,
|
||
...attributes
|
||
} = options;
|
||
return {
|
||
...attributes,
|
||
width: targetWidth,
|
||
height: targetHeight,
|
||
loading: attributes.loading ?? "lazy",
|
||
decoding: attributes.decoding ?? "async"
|
||
};
|
||
},
|
||
getSrcSet(options) {
|
||
const { targetWidth, targetHeight } = getTargetDimensions(options);
|
||
const aspectRatio = targetWidth / targetHeight;
|
||
const { widths, densities } = options;
|
||
const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
|
||
let transformedWidths = (widths ?? []).sort(sortNumeric);
|
||
let imageWidth = options.width;
|
||
let maxWidth = Infinity;
|
||
if (isESMImportedImage(options.src)) {
|
||
imageWidth = options.src.width;
|
||
maxWidth = imageWidth;
|
||
if (transformedWidths.length > 0 && transformedWidths.at(-1) > maxWidth) {
|
||
transformedWidths = transformedWidths.filter((width) => width <= maxWidth);
|
||
transformedWidths.push(maxWidth);
|
||
}
|
||
}
|
||
transformedWidths = Array.from(new Set(transformedWidths));
|
||
const {
|
||
width: transformWidth,
|
||
height: transformHeight,
|
||
...transformWithoutDimensions
|
||
} = options;
|
||
let allWidths = [];
|
||
if (densities) {
|
||
const densityValues = densities.map((density) => {
|
||
if (typeof density === "number") {
|
||
return density;
|
||
} else {
|
||
return parseFloat(density);
|
||
}
|
||
});
|
||
const densityWidths = densityValues.sort(sortNumeric).map((density) => Math.round(targetWidth * density));
|
||
allWidths = densityWidths.map((width, index) => ({
|
||
width,
|
||
descriptor: `${densityValues[index]}x`
|
||
}));
|
||
} else if (transformedWidths.length > 0) {
|
||
allWidths = transformedWidths.map((width) => ({
|
||
width,
|
||
descriptor: `${width}w`
|
||
}));
|
||
}
|
||
return allWidths.map(({ width, descriptor }) => {
|
||
const height = Math.round(width / aspectRatio);
|
||
const transform = { ...transformWithoutDimensions, width, height };
|
||
return {
|
||
transform,
|
||
descriptor,
|
||
attributes: {
|
||
type: `image/${targetFormat}`
|
||
}
|
||
};
|
||
});
|
||
},
|
||
getURL(options, imageConfig) {
|
||
const searchParams = new URLSearchParams();
|
||
if (isESMImportedImage(options.src)) {
|
||
searchParams.append("href", options.src.src);
|
||
} else if (isRemoteAllowed(options.src, imageConfig)) {
|
||
searchParams.append("href", options.src);
|
||
} else {
|
||
return options.src;
|
||
}
|
||
const params = {
|
||
w: "width",
|
||
h: "height",
|
||
q: "quality",
|
||
f: "format",
|
||
fit: "fit",
|
||
position: "position"
|
||
};
|
||
Object.entries(params).forEach(([param, key]) => {
|
||
options[key] && searchParams.append(param, options[key].toString());
|
||
});
|
||
const imageEndpoint = joinPaths("/", imageConfig.endpoint.route);
|
||
return `${imageEndpoint}?${searchParams}`;
|
||
},
|
||
parseURL(url) {
|
||
const params = url.searchParams;
|
||
if (!params.has("href")) {
|
||
return void 0;
|
||
}
|
||
const transform = {
|
||
src: params.get("href"),
|
||
width: params.has("w") ? parseInt(params.get("w")) : void 0,
|
||
height: params.has("h") ? parseInt(params.get("h")) : void 0,
|
||
format: params.get("f"),
|
||
quality: params.get("q"),
|
||
fit: params.get("fit"),
|
||
position: params.get("position") ?? void 0
|
||
};
|
||
return transform;
|
||
}
|
||
};
|
||
function getTargetDimensions(options) {
|
||
let targetWidth = options.width;
|
||
let targetHeight = options.height;
|
||
if (isESMImportedImage(options.src)) {
|
||
const aspectRatio = options.src.width / options.src.height;
|
||
if (targetHeight && !targetWidth) {
|
||
targetWidth = Math.round(targetHeight * aspectRatio);
|
||
} else if (targetWidth && !targetHeight) {
|
||
targetHeight = Math.round(targetWidth / aspectRatio);
|
||
} else if (!targetWidth && !targetHeight) {
|
||
targetWidth = options.src.width;
|
||
targetHeight = options.src.height;
|
||
}
|
||
}
|
||
return {
|
||
targetWidth,
|
||
targetHeight
|
||
};
|
||
}
|
||
|
||
function isImageMetadata(src) {
|
||
return src.fsPath && !("fsPath" in src);
|
||
}
|
||
|
||
const cssFitValues = ["fill", "contain", "cover", "scale-down"];
|
||
function addCSSVarsToStyle(vars, styles) {
|
||
const cssVars = Object.entries(vars).filter(([_, value]) => value !== void 0 && value !== false).map(([key, value]) => `--${key}: ${value};`).join(" ");
|
||
if (!styles) {
|
||
return cssVars;
|
||
}
|
||
const style = typeof styles === "string" ? styles : toStyleString(styles);
|
||
return `${cssVars} ${style}`;
|
||
}
|
||
|
||
const decoder = new TextDecoder();
|
||
const toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end));
|
||
const toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + ("0" + i.toString(16)).slice(-2), "");
|
||
const readInt16LE = (input, offset = 0) => {
|
||
const val = input[offset] + input[offset + 1] * 2 ** 8;
|
||
return val | (val & 2 ** 15) * 131070;
|
||
};
|
||
const readUInt16BE = (input, offset = 0) => input[offset] * 2 ** 8 + input[offset + 1];
|
||
const readUInt16LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8;
|
||
const readUInt24LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16;
|
||
const readInt32LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16 + (input[offset + 3] << 24);
|
||
const readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + input[offset + 1] * 2 ** 16 + input[offset + 2] * 2 ** 8 + input[offset + 3];
|
||
const readUInt32LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16 + input[offset + 3] * 2 ** 24;
|
||
const methods = {
|
||
readUInt16BE,
|
||
readUInt16LE,
|
||
readUInt32BE,
|
||
readUInt32LE
|
||
};
|
||
function readUInt(input, bits, offset, isBigEndian) {
|
||
offset = offset || 0;
|
||
const endian = isBigEndian ? "BE" : "LE";
|
||
const methodName = "readUInt" + bits + endian;
|
||
return methods[methodName](input, offset);
|
||
}
|
||
function readBox(buffer, offset) {
|
||
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
|
||
};
|
||
}
|
||
function findBox(buffer, boxName, offset) {
|
||
while (offset < buffer.length) {
|
||
const box = readBox(buffer, offset);
|
||
if (!box) break;
|
||
if (box.name === boxName) return box;
|
||
offset += box.size;
|
||
}
|
||
}
|
||
|
||
const BMP = {
|
||
validate: (input) => toUTF8String(input, 0, 2) === "BM",
|
||
calculate: (input) => ({
|
||
height: Math.abs(readInt32LE(input, 22)),
|
||
width: readUInt32LE(input, 18)
|
||
})
|
||
};
|
||
|
||
const TYPE_ICON = 1;
|
||
const SIZE_HEADER$1 = 2 + 2 + 2;
|
||
const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4;
|
||
function getSizeFromOffset(input, offset) {
|
||
const value = input[offset];
|
||
return value === 0 ? 256 : value;
|
||
}
|
||
function getImageSize$1(input, imageIndex) {
|
||
const offset = SIZE_HEADER$1 + imageIndex * SIZE_IMAGE_ENTRY;
|
||
return {
|
||
height: getSizeFromOffset(input, offset + 1),
|
||
width: getSizeFromOffset(input, offset)
|
||
};
|
||
}
|
||
const ICO = {
|
||
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$1(input, 0);
|
||
if (nbImages === 1) return imageSize;
|
||
const imgs = [imageSize];
|
||
for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) {
|
||
imgs.push(getImageSize$1(input, imageIndex));
|
||
}
|
||
return {
|
||
height: imageSize.height,
|
||
images: imgs,
|
||
width: imageSize.width
|
||
};
|
||
}
|
||
};
|
||
|
||
const TYPE_CURSOR = 2;
|
||
const CUR = {
|
||
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)
|
||
};
|
||
|
||
const DDS = {
|
||
validate: (input) => readUInt32LE(input, 0) === 542327876,
|
||
calculate: (input) => ({
|
||
height: readUInt32LE(input, 12),
|
||
width: readUInt32LE(input, 16)
|
||
})
|
||
};
|
||
|
||
const gifRegexp = /^GIF8[79]a/;
|
||
const GIF = {
|
||
validate: (input) => gifRegexp.test(toUTF8String(input, 0, 6)),
|
||
calculate: (input) => ({
|
||
height: readUInt16LE(input, 8),
|
||
width: readUInt16LE(input, 6)
|
||
})
|
||
};
|
||
|
||
const brandMap = {
|
||
avif: "avif",
|
||
avis: "avif",
|
||
// avif-sequence
|
||
mif1: "heif",
|
||
msf1: "heif",
|
||
// heif-sequence
|
||
heic: "heic",
|
||
heix: "heic",
|
||
hevc: "heic",
|
||
// heic-sequence
|
||
hevx: "heic"
|
||
// heic-sequence
|
||
};
|
||
function detectBrands(buffer, start, end) {
|
||
let brandsDetected = {};
|
||
for (let i = start; i <= end; i += 4) {
|
||
const brand = toUTF8String(buffer, i, i + 4);
|
||
if (brand in brandMap) {
|
||
brandsDetected[brand] = 1;
|
||
}
|
||
}
|
||
if ("avif" in brandsDetected || "avis" 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";
|
||
}
|
||
}
|
||
const HEIF = {
|
||
validate(buffer) {
|
||
const ftype = toUTF8String(buffer, 4, 8);
|
||
const brand = toUTF8String(buffer, 8, 12);
|
||
return "ftyp" === ftype && brand in brandMap;
|
||
},
|
||
calculate(buffer) {
|
||
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");
|
||
}
|
||
};
|
||
|
||
const SIZE_HEADER = 4 + 4;
|
||
const FILE_LENGTH_OFFSET = 4;
|
||
const ENTRY_LENGTH_OFFSET = 4;
|
||
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(input, imageOffset) {
|
||
const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
|
||
return [
|
||
toUTF8String(input, imageOffset, imageLengthOffset),
|
||
readUInt32BE(input, imageLengthOffset)
|
||
];
|
||
}
|
||
function getImageSize(type) {
|
||
const size = ICON_TYPE_SIZE[type];
|
||
return { width: size, height: size, type };
|
||
}
|
||
const ICNS = {
|
||
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;
|
||
}
|
||
};
|
||
|
||
const J2C = {
|
||
// 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)
|
||
})
|
||
};
|
||
|
||
const JP2 = {
|
||
validate(input) {
|
||
if (readUInt32BE(input, 4) !== 1783636e3 || readUInt32BE(input, 0) < 1) return false;
|
||
const ftypBox = findBox(input, "ftyp", 0);
|
||
if (!ftypBox) return false;
|
||
return readUInt32BE(input, ftypBox.offset + 4) === 1718909296;
|
||
},
|
||
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");
|
||
}
|
||
};
|
||
|
||
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";
|
||
const IDF_ENTRY_BYTES = 12;
|
||
const NUM_DIRECTORY_ENTRIES_BYTES = 2;
|
||
function isEXIF(input) {
|
||
return toHexString(input, 2, 6) === EXIF_MARKER;
|
||
}
|
||
function extractSize(input, index) {
|
||
return {
|
||
height: readUInt16BE(input, index),
|
||
width: readUInt16BE(input, index + 2)
|
||
};
|
||
}
|
||
function extractOrientation(exifBlock, isBigEndian) {
|
||
const idfOffset = 8;
|
||
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;
|
||
if (start > exifBlock.length) {
|
||
return;
|
||
}
|
||
const block = exifBlock.slice(start, end);
|
||
const tagNumber = readUInt(block, 16, 0, isBigEndian);
|
||
if (tagNumber === 274) {
|
||
const dataFormat = readUInt(block, 16, 2, isBigEndian);
|
||
if (dataFormat !== 3) {
|
||
return;
|
||
}
|
||
const numberOfComponents = readUInt(block, 32, 4, isBigEndian);
|
||
if (numberOfComponents !== 1) {
|
||
return;
|
||
}
|
||
return readUInt(block, 16, 8, isBigEndian);
|
||
}
|
||
}
|
||
}
|
||
function validateExifBlock(input, index) {
|
||
const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index);
|
||
const byteAlign = toHexString(
|
||
exifBlock,
|
||
EXIF_HEADER_BYTES,
|
||
EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES
|
||
);
|
||
const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN;
|
||
const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN;
|
||
if (isBigEndian || isLittleEndian) {
|
||
return extractOrientation(exifBlock, isBigEndian);
|
||
}
|
||
}
|
||
function validateInput(input, index) {
|
||
if (index > input.length) {
|
||
throw new TypeError("Corrupt JPG, exceeded buffer limits");
|
||
}
|
||
}
|
||
const JPG = {
|
||
validate: (input) => toHexString(input, 0, 2) === "ffd8",
|
||
calculate(input) {
|
||
input = input.slice(4);
|
||
let orientation;
|
||
let next;
|
||
while (input.length) {
|
||
const i = readUInt16BE(input, 0);
|
||
if (input[i] !== 255) {
|
||
input = input.slice(i);
|
||
continue;
|
||
}
|
||
if (isEXIF(input)) {
|
||
orientation = validateExifBlock(input, i);
|
||
}
|
||
validateInput(input, i);
|
||
next = input[i + 1];
|
||
if (next === 192 || next === 193 || next === 194) {
|
||
const size = extractSize(input, i + 5);
|
||
if (!orientation) {
|
||
return size;
|
||
}
|
||
return {
|
||
height: size.height,
|
||
orientation,
|
||
width: size.width
|
||
};
|
||
}
|
||
input = input.slice(i + 2);
|
||
}
|
||
throw new TypeError("Invalid JPG, no size found");
|
||
}
|
||
};
|
||
|
||
const KTX = {
|
||
validate: (input) => {
|
||
const signature = toUTF8String(input, 1, 7);
|
||
return ["KTX 11", "KTX 20"].includes(signature);
|
||
},
|
||
calculate: (input) => {
|
||
const type = input[5] === 49 ? "ktx" : "ktx2";
|
||
const offset = type === "ktx" ? 36 : 20;
|
||
return {
|
||
height: readUInt32LE(input, offset + 4),
|
||
width: readUInt32LE(input, offset),
|
||
type
|
||
};
|
||
}
|
||
};
|
||
|
||
const pngSignature = "PNG\r\n\n";
|
||
const pngImageHeaderChunkName = "IHDR";
|
||
const pngFriedChunkName = "CgBI";
|
||
const PNG = {
|
||
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)
|
||
};
|
||
}
|
||
};
|
||
|
||
const PNMTypes = {
|
||
P1: "pbm/ascii",
|
||
P2: "pgm/ascii",
|
||
P3: "ppm/ascii",
|
||
P4: "pbm",
|
||
P5: "pgm",
|
||
P6: "ppm",
|
||
P7: "pam",
|
||
PF: "pfm"
|
||
};
|
||
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");
|
||
}
|
||
}
|
||
};
|
||
const PNM = {
|
||
validate: (input) => toUTF8String(input, 0, 2) in PNMTypes,
|
||
calculate(input) {
|
||
const signature = toUTF8String(input, 0, 2);
|
||
const type = PNMTypes[signature];
|
||
const lines = toUTF8String(input, 3).split(/[\r\n]+/);
|
||
const handler = handlers[type] || handlers.default;
|
||
return handler(lines);
|
||
}
|
||
};
|
||
|
||
const PSD = {
|
||
validate: (input) => toUTF8String(input, 0, 4) === "8BPS",
|
||
calculate: (input) => ({
|
||
height: readUInt32BE(input, 14),
|
||
width: readUInt32BE(input, 18)
|
||
})
|
||
};
|
||
|
||
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 void 0;
|
||
}
|
||
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 = extractorRegExps.width.exec(root);
|
||
const height = extractorRegExps.height.exec(root);
|
||
const viewbox = extractorRegExps.viewbox.exec(root);
|
||
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
|
||
};
|
||
}
|
||
const SVG = {
|
||
// Scan only the first kilo-byte to speed up the check on larger files
|
||
validate: (input) => svgReg.test(toUTF8String(input, 0, 1e3)),
|
||
calculate(input) {
|
||
const root = extractorRegExps.root.exec(toUTF8String(input));
|
||
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");
|
||
}
|
||
};
|
||
|
||
const TGA = {
|
||
validate(input) {
|
||
return readUInt16LE(input, 0) === 0 && readUInt16LE(input, 4) === 0;
|
||
},
|
||
calculate(input) {
|
||
return {
|
||
height: readUInt16LE(input, 14),
|
||
width: readUInt16LE(input, 12)
|
||
};
|
||
}
|
||
};
|
||
|
||
function readIFD(input, isBigEndian) {
|
||
const ifdOffset = readUInt(input, 32, 4, isBigEndian);
|
||
return input.slice(ifdOffset + 2);
|
||
}
|
||
function readValue(input, isBigEndian) {
|
||
const low = readUInt(input, 16, 8, isBigEndian);
|
||
const high = readUInt(input, 16, 10, isBigEndian);
|
||
return (high << 16) + low;
|
||
}
|
||
function nextTag(input) {
|
||
if (input.length > 24) {
|
||
return input.slice(12);
|
||
}
|
||
}
|
||
function extractTags(input, isBigEndian) {
|
||
const tags = {};
|
||
let temp = 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);
|
||
if (code === 0) {
|
||
break;
|
||
} else {
|
||
if (length === 1 && (type === 3 || type === 4)) {
|
||
tags[code] = readValue(temp, isBigEndian);
|
||
}
|
||
temp = nextTag(temp);
|
||
}
|
||
}
|
||
return tags;
|
||
}
|
||
function determineEndianness(input) {
|
||
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
|
||
];
|
||
const TIFF = {
|
||
validate: (input) => signatures.includes(toHexString(input, 0, 4)),
|
||
calculate(input) {
|
||
const isBigEndian = determineEndianness(input) === "BE";
|
||
const ifdBuffer = readIFD(input, isBigEndian);
|
||
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 };
|
||
}
|
||
};
|
||
|
||
function calculateExtended(input) {
|
||
return {
|
||
height: 1 + readUInt24LE(input, 7),
|
||
width: 1 + readUInt24LE(input, 4)
|
||
};
|
||
}
|
||
function calculateLossless(input) {
|
||
return {
|
||
height: 1 + ((input[4] & 15) << 10 | input[3] << 2 | (input[2] & 192) >> 6),
|
||
width: 1 + ((input[2] & 63) << 8 | input[1])
|
||
};
|
||
}
|
||
function calculateLossy(input) {
|
||
return {
|
||
height: readInt16LE(input, 8) & 16383,
|
||
width: readInt16LE(input, 6) & 16383
|
||
};
|
||
}
|
||
const WEBP = {
|
||
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);
|
||
if (chunkHeader === "VP8X") {
|
||
const extendedHeader = input[0];
|
||
const validStart = (extendedHeader & 192) === 0;
|
||
const validEnd = (extendedHeader & 1) === 0;
|
||
if (validStart && validEnd) {
|
||
return calculateExtended(input);
|
||
} else {
|
||
throw new TypeError("Invalid WebP");
|
||
}
|
||
}
|
||
if (chunkHeader === "VP8 " && input[0] !== 47) {
|
||
return calculateLossy(input);
|
||
}
|
||
const signature = toHexString(input, 3, 6);
|
||
if (chunkHeader === "VP8L" && signature !== "9d012a") {
|
||
return calculateLossless(input);
|
||
}
|
||
throw new TypeError("Invalid WebP");
|
||
}
|
||
};
|
||
|
||
const typeHandlers = /* @__PURE__ */ 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]
|
||
]);
|
||
const types = Array.from(typeHandlers.keys());
|
||
|
||
const firstBytes = /* @__PURE__ */ new Map([
|
||
[56, "psd"],
|
||
[66, "bmp"],
|
||
[68, "dds"],
|
||
[71, "gif"],
|
||
[73, "tiff"],
|
||
[77, "tiff"],
|
||
[82, "webp"],
|
||
[105, "icns"],
|
||
[137, "png"],
|
||
[255, "jpg"]
|
||
]);
|
||
function detector(input) {
|
||
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));
|
||
}
|
||
|
||
function lookup(input) {
|
||
const type = detector(input);
|
||
if (typeof type !== "undefined") {
|
||
const size = typeHandlers.get(type).calculate(input);
|
||
if (size !== void 0) {
|
||
size.type = size.type ?? type;
|
||
return size;
|
||
}
|
||
}
|
||
throw new TypeError("unsupported file type: " + type);
|
||
}
|
||
|
||
async function imageMetadata(data, src) {
|
||
let result;
|
||
try {
|
||
result = lookup(data);
|
||
} catch {
|
||
throw new AstroError({
|
||
...NoImageMetadata,
|
||
message: NoImageMetadata.message(src)
|
||
});
|
||
}
|
||
if (!result.height || !result.width || !result.type) {
|
||
throw new AstroError({
|
||
...NoImageMetadata,
|
||
message: NoImageMetadata.message(src)
|
||
});
|
||
}
|
||
const { width, height, type, orientation } = result;
|
||
const isPortrait = (orientation || 0) >= 5;
|
||
return {
|
||
width: isPortrait ? height : width,
|
||
height: isPortrait ? width : height,
|
||
format: type,
|
||
orientation
|
||
};
|
||
}
|
||
|
||
async function inferRemoteSize(url) {
|
||
const response = await fetch(url);
|
||
if (!response.body || !response.ok) {
|
||
throw new AstroError({
|
||
...FailedToFetchRemoteImageDimensions,
|
||
message: FailedToFetchRemoteImageDimensions.message(url)
|
||
});
|
||
}
|
||
const reader = response.body.getReader();
|
||
let done, value;
|
||
let accumulatedChunks = new Uint8Array();
|
||
while (!done) {
|
||
const readResult = await reader.read();
|
||
done = readResult.done;
|
||
if (done) break;
|
||
if (readResult.value) {
|
||
value = readResult.value;
|
||
let tmp = new Uint8Array(accumulatedChunks.length + value.length);
|
||
tmp.set(accumulatedChunks, 0);
|
||
tmp.set(value, accumulatedChunks.length);
|
||
accumulatedChunks = tmp;
|
||
try {
|
||
const dimensions = await imageMetadata(accumulatedChunks, url);
|
||
if (dimensions) {
|
||
await reader.cancel();
|
||
return dimensions;
|
||
}
|
||
} catch {
|
||
}
|
||
}
|
||
}
|
||
throw new AstroError({
|
||
...NoImageMetadata,
|
||
message: NoImageMetadata.message(url)
|
||
});
|
||
}
|
||
|
||
async function getConfiguredImageService() {
|
||
if (!globalThis?.astroAsset?.imageService) {
|
||
const { default: service } = await import(
|
||
// @ts-expect-error
|
||
'./sharp_DRkwzUY0.mjs'
|
||
).catch((e) => {
|
||
const error = new AstroError(InvalidImageService);
|
||
error.cause = e;
|
||
throw error;
|
||
});
|
||
if (!globalThis.astroAsset) globalThis.astroAsset = {};
|
||
globalThis.astroAsset.imageService = service;
|
||
return service;
|
||
}
|
||
return globalThis.astroAsset.imageService;
|
||
}
|
||
async function getImage$1(options, imageConfig) {
|
||
if (!options || typeof options !== "object") {
|
||
throw new AstroError({
|
||
...ExpectedImageOptions,
|
||
message: ExpectedImageOptions.message(JSON.stringify(options))
|
||
});
|
||
}
|
||
if (typeof options.src === "undefined") {
|
||
throw new AstroError({
|
||
...ExpectedImage,
|
||
message: ExpectedImage.message(
|
||
options.src,
|
||
"undefined",
|
||
JSON.stringify(options)
|
||
)
|
||
});
|
||
}
|
||
if (isImageMetadata(options)) {
|
||
throw new AstroError(ExpectedNotESMImage);
|
||
}
|
||
const service = await getConfiguredImageService();
|
||
const resolvedOptions = {
|
||
...options,
|
||
src: await resolveSrc(options.src)
|
||
};
|
||
let originalWidth;
|
||
let originalHeight;
|
||
if (options.inferSize && isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
|
||
const result = await inferRemoteSize(resolvedOptions.src);
|
||
resolvedOptions.width ??= result.width;
|
||
resolvedOptions.height ??= result.height;
|
||
originalWidth = result.width;
|
||
originalHeight = result.height;
|
||
delete resolvedOptions.inferSize;
|
||
}
|
||
const originalFilePath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : void 0;
|
||
const clonedSrc = isESMImportedImage(resolvedOptions.src) ? (
|
||
// @ts-expect-error - clone is a private, hidden prop
|
||
resolvedOptions.src.clone ?? resolvedOptions.src
|
||
) : resolvedOptions.src;
|
||
if (isESMImportedImage(clonedSrc)) {
|
||
originalWidth = clonedSrc.width;
|
||
originalHeight = clonedSrc.height;
|
||
}
|
||
if (originalWidth && originalHeight) {
|
||
const aspectRatio = originalWidth / originalHeight;
|
||
if (resolvedOptions.height && !resolvedOptions.width) {
|
||
resolvedOptions.width = Math.round(resolvedOptions.height * aspectRatio);
|
||
} else if (resolvedOptions.width && !resolvedOptions.height) {
|
||
resolvedOptions.height = Math.round(resolvedOptions.width / aspectRatio);
|
||
} else if (!resolvedOptions.width && !resolvedOptions.height) {
|
||
resolvedOptions.width = originalWidth;
|
||
resolvedOptions.height = originalHeight;
|
||
}
|
||
}
|
||
resolvedOptions.src = clonedSrc;
|
||
const layout = options.layout ?? imageConfig.layout ?? "none";
|
||
if (resolvedOptions.priority) {
|
||
resolvedOptions.loading ??= "eager";
|
||
resolvedOptions.decoding ??= "sync";
|
||
resolvedOptions.fetchpriority ??= "high";
|
||
delete resolvedOptions.priority;
|
||
} else {
|
||
resolvedOptions.loading ??= "lazy";
|
||
resolvedOptions.decoding ??= "async";
|
||
resolvedOptions.fetchpriority ??= "auto";
|
||
}
|
||
if (layout !== "none") {
|
||
resolvedOptions.widths ||= getWidths({
|
||
width: resolvedOptions.width,
|
||
layout,
|
||
originalWidth,
|
||
breakpoints: imageConfig.breakpoints?.length ? imageConfig.breakpoints : isLocalService(service) ? LIMITED_RESOLUTIONS : DEFAULT_RESOLUTIONS
|
||
});
|
||
resolvedOptions.sizes ||= getSizesAttribute({ width: resolvedOptions.width, layout });
|
||
delete resolvedOptions.densities;
|
||
resolvedOptions.style = addCSSVarsToStyle(
|
||
{
|
||
fit: cssFitValues.includes(resolvedOptions.fit ?? "") && resolvedOptions.fit,
|
||
pos: resolvedOptions.position
|
||
},
|
||
resolvedOptions.style
|
||
);
|
||
resolvedOptions["data-astro-image"] = layout;
|
||
}
|
||
const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
|
||
const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
|
||
let imageURL = await service.getURL(validatedOptions, imageConfig);
|
||
const matchesValidatedTransform = (transform) => transform.width === validatedOptions.width && transform.height === validatedOptions.height && transform.format === validatedOptions.format;
|
||
let srcSets = await Promise.all(
|
||
srcSetTransforms.map(async (srcSet) => {
|
||
return {
|
||
transform: srcSet.transform,
|
||
url: matchesValidatedTransform(srcSet.transform) ? imageURL : await service.getURL(srcSet.transform, imageConfig),
|
||
descriptor: srcSet.descriptor,
|
||
attributes: srcSet.attributes
|
||
};
|
||
})
|
||
);
|
||
if (isLocalService(service) && globalThis.astroAsset.addStaticImage && !(isRemoteImage(validatedOptions.src) && imageURL === validatedOptions.src)) {
|
||
const propsToHash = service.propertiesToHash ?? DEFAULT_HASH_PROPS;
|
||
imageURL = globalThis.astroAsset.addStaticImage(
|
||
validatedOptions,
|
||
propsToHash,
|
||
originalFilePath
|
||
);
|
||
srcSets = srcSetTransforms.map((srcSet) => {
|
||
return {
|
||
transform: srcSet.transform,
|
||
url: matchesValidatedTransform(srcSet.transform) ? imageURL : globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalFilePath),
|
||
descriptor: srcSet.descriptor,
|
||
attributes: srcSet.attributes
|
||
};
|
||
});
|
||
}
|
||
return {
|
||
rawOptions: resolvedOptions,
|
||
options: validatedOptions,
|
||
src: imageURL,
|
||
srcSet: {
|
||
values: srcSets,
|
||
attribute: srcSets.map((srcSet) => `${srcSet.url} ${srcSet.descriptor}`).join(", ")
|
||
},
|
||
attributes: service.getHTMLAttributes !== void 0 ? await service.getHTMLAttributes(validatedOptions, imageConfig) : {}
|
||
};
|
||
}
|
||
|
||
const $$Astro$2 = createAstro();
|
||
const $$Image = createComponent(async ($$result, $$props, $$slots) => {
|
||
const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots);
|
||
Astro2.self = $$Image;
|
||
const props = Astro2.props;
|
||
if (props.alt === void 0 || props.alt === null) {
|
||
throw new AstroError(ImageMissingAlt);
|
||
}
|
||
if (typeof props.width === "string") {
|
||
props.width = parseInt(props.width);
|
||
}
|
||
if (typeof props.height === "string") {
|
||
props.height = parseInt(props.height);
|
||
}
|
||
const layout = props.layout ?? imageConfig.layout ?? "none";
|
||
if (layout !== "none") {
|
||
props.layout ??= imageConfig.layout;
|
||
props.fit ??= imageConfig.objectFit ?? "cover";
|
||
props.position ??= imageConfig.objectPosition ?? "center";
|
||
}
|
||
const image = await getImage(props);
|
||
const additionalAttributes = {};
|
||
if (image.srcSet.values.length > 0) {
|
||
additionalAttributes.srcset = image.srcSet.attribute;
|
||
}
|
||
const { class: className, ...attributes } = { ...additionalAttributes, ...image.attributes };
|
||
return renderTemplate`${maybeRenderHead()}<img${addAttribute(image.src, "src")}${spreadAttributes(attributes)}${addAttribute(className, "class")}>`;
|
||
}, "/root/homewebsite/node_modules/astro/components/Image.astro", void 0);
|
||
|
||
const $$Astro$1 = createAstro();
|
||
const $$Picture = createComponent(async ($$result, $$props, $$slots) => {
|
||
const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots);
|
||
Astro2.self = $$Picture;
|
||
const defaultFormats = ["webp"];
|
||
const defaultFallbackFormat = "png";
|
||
const specialFormatsFallback = ["gif", "svg", "jpg", "jpeg"];
|
||
const { formats = defaultFormats, pictureAttributes = {}, fallbackFormat, ...props } = Astro2.props;
|
||
if (props.alt === void 0 || props.alt === null) {
|
||
throw new AstroError(ImageMissingAlt);
|
||
}
|
||
const scopedStyleClass = props.class?.match(/\bastro-\w{8}\b/)?.[0];
|
||
if (scopedStyleClass) {
|
||
if (pictureAttributes.class) {
|
||
pictureAttributes.class = `${pictureAttributes.class} ${scopedStyleClass}`;
|
||
} else {
|
||
pictureAttributes.class = scopedStyleClass;
|
||
}
|
||
}
|
||
const layout = props.layout ?? imageConfig.layout ?? "none";
|
||
const useResponsive = layout !== "none";
|
||
if (useResponsive) {
|
||
props.layout ??= imageConfig.layout;
|
||
props.fit ??= imageConfig.objectFit ?? "cover";
|
||
props.position ??= imageConfig.objectPosition ?? "center";
|
||
}
|
||
for (const key in props) {
|
||
if (key.startsWith("data-astro-cid")) {
|
||
pictureAttributes[key] = props[key];
|
||
}
|
||
}
|
||
const originalSrc = await resolveSrc(props.src);
|
||
const optimizedImages = await Promise.all(
|
||
formats.map(
|
||
async (format) => await getImage({
|
||
...props,
|
||
src: originalSrc,
|
||
format,
|
||
widths: props.widths,
|
||
densities: props.densities
|
||
})
|
||
)
|
||
);
|
||
let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat;
|
||
if (!fallbackFormat && isESMImportedImage(originalSrc) && specialFormatsFallback.includes(originalSrc.format)) {
|
||
resultFallbackFormat = originalSrc.format;
|
||
}
|
||
const fallbackImage = await getImage({
|
||
...props,
|
||
format: resultFallbackFormat,
|
||
widths: props.widths,
|
||
densities: props.densities
|
||
});
|
||
const imgAdditionalAttributes = {};
|
||
const sourceAdditionalAttributes = {};
|
||
if (props.sizes) {
|
||
sourceAdditionalAttributes.sizes = props.sizes;
|
||
}
|
||
if (fallbackImage.srcSet.values.length > 0) {
|
||
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
|
||
}
|
||
const { class: className, ...attributes } = {
|
||
...imgAdditionalAttributes,
|
||
...fallbackImage.attributes
|
||
};
|
||
return renderTemplate`${maybeRenderHead()}<picture${spreadAttributes(pictureAttributes)}> ${Object.entries(optimizedImages).map(([_, image]) => {
|
||
const srcsetAttribute = props.densities || !props.densities && !props.widths && !useResponsive ? `${image.src}${image.srcSet.values.length > 0 ? ", " + image.srcSet.attribute : ""}` : image.srcSet.attribute;
|
||
return renderTemplate`<source${addAttribute(srcsetAttribute, "srcset")}${addAttribute(mime.lookup(image.options.format ?? image.src) ?? `image/${image.options.format}`, "type")}${spreadAttributes(sourceAdditionalAttributes)}>`;
|
||
})} <img${addAttribute(fallbackImage.src, "src")}${spreadAttributes(attributes)}${addAttribute(className, "class")}> </picture>`;
|
||
}, "/root/homewebsite/node_modules/astro/components/Picture.astro", void 0);
|
||
|
||
const fontsMod = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||
__proto__: null
|
||
}, Symbol.toStringTag, { value: 'Module' }));
|
||
|
||
const $$Astro = createAstro();
|
||
const $$Font = createComponent(($$result, $$props, $$slots) => {
|
||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||
Astro2.self = $$Font;
|
||
const { internalConsumableMap } = fontsMod;
|
||
if (!internalConsumableMap) {
|
||
throw new AstroError(ExperimentalFontsNotEnabled);
|
||
}
|
||
const { cssVariable, preload = false } = Astro2.props;
|
||
const data = internalConsumableMap.get(cssVariable);
|
||
if (!data) {
|
||
throw new AstroError({
|
||
...FontFamilyNotFound,
|
||
message: FontFamilyNotFound.message(cssVariable)
|
||
});
|
||
}
|
||
return renderTemplate`<style>${unescapeHTML(data.css)}</style>${preload && data.preloadData.map(({ url, type }) => renderTemplate`<link rel="preload"${addAttribute(url, "href")} as="font"${addAttribute(`font/${type}`, "type")} crossorigin>`)}`;
|
||
}, "/root/homewebsite/node_modules/astro/components/Font.astro", void 0);
|
||
|
||
const imageConfig = {"endpoint":{"route":"/_image","entrypoint":"astro/assets/endpoint/node"},"service":{"entrypoint":"astro/assets/services/sharp","config":{}},"domains":[],"remotePatterns":[],"responsiveStyles":false};
|
||
// This is used by the @astrojs/node integration to locate images.
|
||
// It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
|
||
// new URL("dist/...") is interpreted by the bundler as a signal to include that directory
|
||
// in the Lambda bundle, which would bloat the bundle with images.
|
||
// To prevent this, we mark the URL construction as pure,
|
||
// so that it's tree-shaken away for all platforms that don't need it.
|
||
const outDir = /* #__PURE__ */ new URL("file:///root/homewebsite/dist/client/");
|
||
const getImage = async (options) => await getImage$1(options, imageConfig);
|
||
|
||
const fnv1a52 = (str) => {
|
||
const len = str.length;
|
||
let i = 0, t0 = 0, v0 = 8997, t1 = 0, v1 = 33826, t2 = 0, v2 = 40164, t3 = 0, v3 = 52210;
|
||
while (i < len) {
|
||
v0 ^= str.charCodeAt(i++);
|
||
t0 = v0 * 435;
|
||
t1 = v1 * 435;
|
||
t2 = v2 * 435;
|
||
t3 = v3 * 435;
|
||
t2 += v0 << 8;
|
||
t3 += v1 << 8;
|
||
t1 += t0 >>> 16;
|
||
v0 = t0 & 65535;
|
||
t2 += t1 >>> 16;
|
||
v1 = t1 & 65535;
|
||
v3 = t3 + (t2 >>> 16) & 65535;
|
||
v2 = t2 & 65535;
|
||
}
|
||
return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ v3 >> 4);
|
||
};
|
||
const etag = (payload, weak = false) => {
|
||
const prefix = weak ? 'W/"' : '"';
|
||
return prefix + fnv1a52(payload).toString(36) + payload.length.toString(36) + '"';
|
||
};
|
||
|
||
async function loadRemoteImage(src) {
|
||
try {
|
||
const res = await fetch(src);
|
||
if (!res.ok) {
|
||
return void 0;
|
||
}
|
||
return Buffer.from(await res.arrayBuffer());
|
||
} catch {
|
||
return void 0;
|
||
}
|
||
}
|
||
const handleImageRequest = async ({
|
||
request,
|
||
loadLocalImage
|
||
}) => {
|
||
const imageService = await getConfiguredImageService();
|
||
if (!("transform" in imageService)) {
|
||
throw new Error("Configured image service is not a local service");
|
||
}
|
||
const url = new URL(request.url);
|
||
const transform = await imageService.parseURL(url, imageConfig);
|
||
if (!transform?.src) {
|
||
return new Response("Invalid request", { status: 400 });
|
||
}
|
||
let inputBuffer = void 0;
|
||
if (isRemotePath(transform.src)) {
|
||
if (!isRemoteAllowed(transform.src, imageConfig)) {
|
||
return new Response("Forbidden", { status: 403 });
|
||
}
|
||
inputBuffer = await loadRemoteImage(new URL(transform.src));
|
||
} else {
|
||
inputBuffer = await loadLocalImage(removeQueryString(transform.src), url);
|
||
}
|
||
if (!inputBuffer) {
|
||
return new Response("Internal Server Error", { status: 500 });
|
||
}
|
||
const { data, format } = await imageService.transform(inputBuffer, transform, imageConfig);
|
||
return new Response(data, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": mime.lookup(format) ?? `image/${format}`,
|
||
"Cache-Control": "public, max-age=31536000",
|
||
ETag: etag(data.toString()),
|
||
Date: (/* @__PURE__ */ new Date()).toUTCString()
|
||
}
|
||
});
|
||
};
|
||
|
||
async function loadLocalImage(src, url) {
|
||
const idx = url.pathname.indexOf("/_image");
|
||
if (idx > 0) {
|
||
src = src.slice(idx);
|
||
}
|
||
if (!URL.canParse("." + src, outDir)) {
|
||
return void 0;
|
||
}
|
||
const fileUrl = new URL("." + src, outDir);
|
||
if (fileUrl.protocol !== "file:") {
|
||
return void 0;
|
||
}
|
||
if (!isParentDirectory(fileURLToPath(outDir), fileURLToPath(fileUrl))) {
|
||
return void 0;
|
||
}
|
||
try {
|
||
return await readFile(fileUrl);
|
||
} catch {
|
||
return void 0;
|
||
}
|
||
}
|
||
const GET = async ({ request }) => {
|
||
try {
|
||
return await handleImageRequest({ request, loadLocalImage });
|
||
} catch (err) {
|
||
console.error("Could not process image request:", err);
|
||
return new Response("Internal Server Error", {
|
||
status: 500
|
||
});
|
||
}
|
||
};
|
||
|
||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||
__proto__: null,
|
||
GET
|
||
}, Symbol.toStringTag, { value: 'Module' }));
|
||
|
||
const page = () => _page;
|
||
|
||
export { page as a, baseService as b, parseQuality as p };
|