1export const MARKDOWN_IMAGE_MAX_WIDTH = 480;
2const MARKDOWN_IMAGE_MAX_HEIGHT = 480;
3
4export interface MarkdownImageDisplaySize {
5 readonly width: number;
6 readonly height: number;
7}
8
9/** SVG percentages need a viewport; use the intrinsic viewBox in that case. */
10export function resolveSvgImageSize(props: Readonly<Record<string, unknown>>) {
11 const dimension = (value: unknown) => {
12 if (typeof value === "number") return value;
13 if (typeof value !== "string" || !/^\s*\d+(?:\.\d+)?(?:px)?\s*$/.test(value)) return NaN;
14 return Number.parseFloat(value);
15 };
16 const width = dimension(props.width);
17 const height = dimension(props.height);
18 if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0)
19 return { width, height };
20 const viewBox =
21 typeof props.viewBox === "string"
22 ? props.viewBox
23 .trim()
24 .split(/[\s,]+/)
25 .map(Number)
26 : [];
27 if (
28 viewBox.length !== 4 ||
29 !viewBox.every(Number.isFinite) ||
30 viewBox[2]! <= 0 ||
31 viewBox[3]! <= 0
32 )
33 return null;
34 return { width: viewBox[2]!, height: viewBox[3]! };
35}
36
37export function resolveMarkdownImageDisplaySize(input: {
38 readonly sourceWidth: number;
39 readonly sourceHeight: number;
40 readonly availableWidth: number;
41}): MarkdownImageDisplaySize | null {
42 if (
43 !Number.isFinite(input.sourceWidth) ||
44 !Number.isFinite(input.sourceHeight) ||
45 !Number.isFinite(input.availableWidth) ||
46 input.sourceWidth <= 0 ||
47 input.sourceHeight <= 0 ||
48 input.availableWidth <= 0
49 ) {
50 return null;
51 }
52
53 const scale = Math.min(
54 1,
55 input.availableWidth / input.sourceWidth,
56 MARKDOWN_IMAGE_MAX_WIDTH / input.sourceWidth,
57 MARKDOWN_IMAGE_MAX_HEIGHT / input.sourceHeight,
58 );
59
60 return {
61 width: input.sourceWidth * scale,
62 height: input.sourceHeight * scale,
63 };
64}