shadimage
retab
uifile-thumbnailsource: ok · refreshed

File Thumbnail

Render complete file thumbnails from browser files, URLs, blobs, text sources, external images, or custom preview content.

npx shadcn add "https://ui.retab.com/r/file-thumbnail.json"
free

Where it goes

testimonial

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
@e965/xlsx@0.20.3dompurify@^3.3.3marked@^15.0.12@kenjiuno/msgreader@^1.28.0pptxviewjs@1.1.9utif@^3.1.0docx-preview@^0.3.7

Registry dependencies

@retab/file-thumbnail-frame@retab/pdf-document-resource@retab/docx-document-resource@retab/csv@retab/xlsx-worker-protocol@retab/utils@retab/pptx-viewer

Files46 · source cached 10/8/2026, 10:47:42 AM

registry/new-york-v4/ui/file-thumbnail.tsx
"use client";

import type { ViewerSource } from "@/lib/viewer-source";
import { GeneratedFileThumbnail } from "@/components/file-thumbnail/generated-preview";

import {
  FileThumbnailFrame,
  FileThumbnailShimmer,
  hasRenderablePreviewContent,
  resolveFileThumbnailState,
} from "./file-thumbnail-frame";
import type {
  FileThumbnailFrameProps,
  FileThumbnailProps,
  FileThumbnailShape,
  FileThumbnailSize,
  FileThumbnailSource,
  FileThumbnailState,
  ThumbnailFile,
} from "./file-thumbnail-types";

export {
  FileThumbnailFrame,
  FileThumbnailShimmer,
  hasRenderablePreviewContent,
  resolveFileThumbnailState,
};
export type {
  FileThumbnailFrameProps,
  FileThumbnailProps,
  FileThumbnailShape,
  FileThumbnailSize,
  FileThumbnailSource,
  FileThumbnailState,
  ThumbnailFile,
};

/**
 * Render a complete file thumbnail from a browser File, a viewer source, an
 * externally generated image, custom preview content, or plain file metadata.
 */
export function FileThumbnail({
  source,
  file,
  as,
  anchor,
  presentation = "document",
  retryKey,
  onError,
  ...frameProps
}: FileThumbnailProps) {
  const resolvedSource = resolveFileThumbnailSource(source, file);
  const accessibilityProps =
    presentation === "decorative"
      ? ({
          "aria-hidden": true,
          role: "presentation",
        } as const)
      : {};
  const {
    previewClassName: _previewClassName,
    previewContent: _previewContent,
    previewImageUrl: _previewImageUrl,
    onPreviewError: _onPreviewError,
    state: _state,
    ...thumbnailProps
  } = frameProps;

  if (shouldUseFrame(frameProps) || !resolvedSource) {
    return (
      <FileThumbnailFrame
        {...frameProps}
        {...accessibilityProps}
        file={file ?? fileFromSource(resolvedSource)}
      />
    );
  }

  return (
    <GeneratedFileThumbnail
      {...thumbnailProps}
      {...accessibilityProps}
      source={resolvedSource}
      as={as}
      anchor={anchor ?? "top-left"}
      retryKey={retryKey}
      onError={onError}
    />
  );
}

function shouldUseFrame({
  previewContent,
  previewImageUrl,
  state,
}: Pick<
  FileThumbnailProps,
  "previewContent" | "previewImageUrl" | "state"
>): boolean {
  return (
    hasRenderablePreviewContent(previewContent) ||
    Boolean(previewImageUrl) ||
    state !== undefined
  );
}

function resolveFileThumbnailSource(
  source: FileThumbnailProps["source"],
  file: FileThumbnailProps["file"],
): ViewerSource | null {
  if (isFile(source)) return fileSource(source);
  if (source) return source;
  if (isFile(file)) return fileSource(file);
  return null;
}

function isFile(value: unknown): value is File {
  return typeof File !== "undefined" && value instanceof File;
}

function fileSource(file: File): ViewerSource {
  return {
    kind: "blob",
    blob: file,
    identityKey: `${file.name}-${file.size}-${file.lastModified}`,
    fileName: file.name,
    mimeType: file.type,
  };
}

function fileFromSource(source: ViewerSource | null): ThumbnailFile {
  if (!source) return { name: "file", type: "" };
  return {
    name: source.fileName ?? "file",
    type: source.mimeType ?? "",
  };
}
registry/new-york-v4/ui/file-thumbnail-types.ts
import type * as React from "react";

import type { ViewerErrorInfo } from "@/lib/viewer-errors";
import type { FileCategory, ViewerSource } from "@/lib/viewer-source";
import type { ThumbnailAnchor } from "@/components/file-thumbnail/types";

import type {
  FileThumbnailFrameProps,
  FileThumbnailShape,
  FileThumbnailSize,
  FileThumbnailState,
  ThumbnailFile,
} from "./file-thumbnail-frame-types";

export type {
  FileThumbnailFrameProps,
  FileThumbnailShape,
  FileThumbnailSize,
  FileThumbnailState,
  ThumbnailFile,
} from "./file-thumbnail-frame-types";

export type FileThumbnailSource = ViewerSource | File;

export interface FileThumbnailProps
  extends Omit<FileThumbnailFrameProps, "file" | "onError"> {
  /** File metadata renders a static shell; a browser File renders a generated preview. */
  file?: ThumbnailFile | File;
  /** URL, Blob/File, or inline text source for generated file thumbnails. */
  source?: FileThumbnailSource;
  /** Override auto-detection when name or MIME type is missing or wrong. */
  as?: FileCategory;
  /** Corner pinned when rendered content overflows the frame. */
  anchor?: ThumbnailAnchor;
  /** Hide rich preview internals from assistive tech when the parent row owns the accessible name. */
  presentation?: "document" | "decorative";
  /** Change to retry a failed generated thumbnail for the same source. */
  retryKey?: React.Key;
  /** Receives generated thumbnail failures with canonical viewer error info. */
  onError?: (error: unknown, info: ViewerErrorInfo) => void;
}
components/file-thumbnail/descriptor.ts
import {
  extensionOf,
  resolveViewerDescriptor,
  type FileCategory,
  type ViewerDescriptor,
  type ViewerSource,
} from "@/lib/viewer-source";

export function resolveThumbnailDescriptor({
  source,
  as,
}: {
  source: ViewerSource;
  as?: FileCategory;
}): ViewerDescriptor {
  return resolveViewerDescriptor({ source, category: as });
}

export function isTiffDescriptor(descriptor: ViewerDescriptor): boolean {
  const mime = descriptor.mimeType?.toLowerCase().split(";")[0].trim();
  if (mime === "image/tiff" || mime === "image/tif") return true;
  const extension = extensionOf(descriptor.displayName);
  return extension === "tif" || extension === "tiff";
}
components/file-thumbnail/errors.tsx
"use client";

import * as React from "react";

// A failed parse updates the outer FileThumbnail state so it degrades to the
// shared fallback surface instead of an empty preview layer.
export class ThumbnailErrorBoundary extends React.Component<
  {
    children: React.ReactNode;
    fallback: React.ReactNode;
    onError: (error: unknown) => void;
  },
  { failed: boolean }
> {
  constructor(props: {
    children: React.ReactNode;
    fallback: React.ReactNode;
    onError: (error: unknown) => void;
  }) {
    super(props);
    this.state = { failed: false };
  }

  static getDerivedStateFromError() {
    return { failed: true };
  }

  componentDidCatch(error: unknown) {
    this.props.onError(error);
  }

  render() {
    if (this.state.failed) return this.props.fallback;
    return this.props.children;
  }
}
components/file-thumbnail/generated-preview.tsx
"use client";

import * as React from "react";

import type { ViewerErrorInfo } from "@/lib/viewer-errors";
import { createViewerResource } from "@/lib/viewer-resource";
import type { FileCategory, ViewerSource } from "@/lib/viewer-source";
import {
  FileThumbnailFrame,
  type FileThumbnailFrameProps,
} from "@/components/ui/file-thumbnail-frame";

import { resolveThumbnailDescriptor } from "./descriptor";
import { getThumbnailKey, getThumbnailRenderKey } from "./keys";
import { ThumbnailClientPreview } from "./thumbnail-client-preview";
import {
  DirectImageThumbnail,
  isDirectImageThumbnail,
} from "./thumbnail-direct-image";
import {
  createThumbnailErrorState,
  type ThumbnailErrorState,
} from "./thumbnail-error-state";
import { getThumbnailOptions } from "./thumbnail-options";
import type { ThumbnailAnchor } from "./types";

export interface GeneratedFileThumbnailProps
  extends Omit<FileThumbnailFrameProps, "file" | "onError"> {
  source: ViewerSource;
  as?: FileCategory;
  anchor: ThumbnailAnchor;
  retryKey?: React.Key;
  onError?: (error: unknown, info: ViewerErrorInfo) => void;
}

export function GeneratedFileThumbnail({
  source,
  as,
  className,
  previewAspectRatio,
  anchor,
  retryKey,
  onError,
  ...props
}: GeneratedFileThumbnailProps) {
  const descriptor = resolveThumbnailDescriptor({ source, as });
  const resource = React.useMemo(() => createViewerResource(source), [source]);
  const thumbnailKey = getThumbnailKey({
    resource,
    descriptor,
    options: getThumbnailOptions(descriptor),
  });
  const renderKey = getThumbnailRenderKey({
    thumbnailKey,
    anchor,
    retryKey: retryKey ?? null,
  });
  const [errorState, setErrorState] =
    React.useState<ThumbnailErrorState | null>(null);
  const currentErrorState =
    errorState?.renderKey === renderKey ? errorState : null;
  const directUrl = resource.content.directUrl;

  if (descriptor.category === "unsupported") {
    return (
      <FileThumbnailFrame
        {...props}
        file={{ name: descriptor.displayName, type: descriptor.mimeType ?? "" }}
        previewAspectRatio={previewAspectRatio}
        className={className}
      />
    );
  }

  if (isDirectImageThumbnail({ descriptor, directUrl })) {
    return (
      <DirectImageThumbnail
        descriptor={descriptor}
        resource={resource}
        directUrl={directUrl!}
        previewAspectRatio={previewAspectRatio}
        className={className}
        anchor={anchor}
        renderKey={renderKey}
        errorState={currentErrorState}
        thumbnailProps={props}
        onError={(error, nextErrorState) => {
          setErrorState(nextErrorState);
          onError?.(error, nextErrorState.info);
        }}
      />
    );
  }

  return (
    <FileThumbnailFrame
      {...props}
      file={{ name: descriptor.displayName, type: descriptor.mimeType ?? "" }}
      previewAspectRatio={previewAspectRatio}
      className={className}
      state={currentErrorState ? "error" : "loaded"}
      aria-label={currentErrorState?.info.userMessage}
      title={currentErrorState?.info.userMessage}
      data-error-domain={currentErrorState?.info.domain}
      data-error-format={currentErrorState?.info.format}
      data-error-kind={currentErrorState?.info.kind}
      data-error-message={currentErrorState?.info.message}
      previewContent={
        <ThumbnailClientPreview
          key={renderKey}
          resource={resource}
          descriptor={descriptor}
          thumbnailKey={thumbnailKey}
          anchor={anchor}
          onError={(error) => {
            const nextErrorState = createThumbnailErrorState({
              renderKey,
              error,
              resource,
              descriptor,
            });
            setErrorState(nextErrorState);
            onError?.(error, nextErrorState.info);
          }}
        />
      }
    />
  );
}
components/file-thumbnail/keys.ts
import type * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import type { ViewerDescriptor } from "@/lib/viewer-source";

import type { ThumbnailAnchor } from "./types";

export interface ThumbnailKeyInput {
  resource: ViewerResource;
  descriptor: ViewerDescriptor;
  unit?: string;
  options?: readonly ThumbnailOption[];
}

export interface ThumbnailRenderKeyInput {
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
  retryKey: React.Key | null;
}

export function getThumbnailKey({
  resource,
  descriptor,
  unit = "first",
  options = [],
}: ThumbnailKeyInput): string {
  return [
    encodePart(`resource:${resource.keys.load}`),
    encodePart(`category:${descriptor.category}`),
    encodePart(`unit:${unit}`),
    ...options.map((option) => encodePart(`option:${option}`)),
  ].join("");
}

export type ThumbnailOption = string & {
  readonly __thumbnailOption: unique symbol;
};

export function thumbnailOption(
  name: string,
  value: string | number,
): ThumbnailOption {
  return `${name}:${String(value)}` as ThumbnailOption;
}

export function getThumbnailRenderKey({
  thumbnailKey,
  anchor,
  retryKey,
}: ThumbnailRenderKeyInput): string {
  return [
    encodePart(`thumbnail:${thumbnailKey}`),
    encodePart(`anchor:${anchor}`),
    encodeRetryKey(retryKey),
  ].join("");
}

function encodePart(value: string): string {
  return `${value.length}:${value}`;
}

function encodeRetryKey(retryKey: React.Key | null): string {
  if (retryKey === null) return encodePart("retry:null:");
  return encodePart(`retry:${typeof retryKey}:${String(retryKey)}`);
}
components/file-thumbnail/renderer-registry.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import type { FileCategory, ViewerDescriptor } from "@/lib/viewer-source";

import { isTiffDescriptor } from "./descriptor";
import { CodeThumbnail } from "./renderers/code-thumbnail";
import { CsvFirstRows } from "./renderers/csv-thumbnail";
import { DocxFirstPage } from "./renderers/docx-thumbnail";
import { HtmlFirstPage } from "./renderers/html-thumbnail";
import { ImageFirstFrame } from "./renderers/image-thumbnail";
import { MarkdownFirstPage } from "./renderers/markdown-thumbnail";
import { MsgEmailThumbnail } from "./renderers/msg-thumbnail";
import { PdfFirstPage } from "./renderers/pdf-thumbnail";
import { PptxFirstSlide } from "./renderers/pptx-thumbnail";
import { TextThumbnail } from "./renderers/text-thumbnail";
import { TiffFirstPage } from "./renderers/tiff-thumbnail";
import { XlsxFirstSheet } from "./renderers/xlsx-thumbnail";
import { isCodeThumbnailDescriptor } from "./thumbnail-code";
import type { ThumbnailAnchor } from "./types";

type DocumentRenderer = (props: {
  resource: ViewerResource;
  descriptor: ViewerDescriptor;
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) => React.ReactNode;

const DOCUMENT_RENDERERS: Record<
  Exclude<FileCategory, "unsupported">,
  DocumentRenderer
> = {
  pdf: ({ resource, anchor }) => (
    <PdfFirstPage resource={resource} anchor={anchor} />
  ),
  xlsx: ({ resource, thumbnailKey }) => (
    <XlsxFirstSheet resource={resource} thumbnailKey={thumbnailKey} />
  ),
  pptx: ({ resource, thumbnailKey, anchor }) => (
    <PptxFirstSlide
      resource={resource}
      thumbnailKey={thumbnailKey}
      anchor={anchor}
    />
  ),
  docx: ({ resource }) => <DocxFirstPage resource={resource} />,
  image: ({ resource, descriptor, thumbnailKey, anchor, onError }) =>
    isTiffDescriptor(descriptor) ? (
      <TiffFirstPage
        resource={resource}
        thumbnailKey={thumbnailKey}
        anchor={anchor}
        onError={onError}
      />
    ) : (
      <ImageFirstFrame resource={resource} anchor={anchor} onError={onError} />
    ),
  csv: ({ resource, thumbnailKey }) => (
    <CsvFirstRows resource={resource} thumbnailKey={thumbnailKey} />
  ),
  markdown: ({ resource, thumbnailKey }) => (
    <MarkdownFirstPage resource={resource} thumbnailKey={thumbnailKey} />
  ),
  html: ({ resource, thumbnailKey }) => (
    <HtmlFirstPage resource={resource} thumbnailKey={thumbnailKey} />
  ),
  email: ({ resource, thumbnailKey }) => (
    <MsgEmailThumbnail resource={resource} thumbnailKey={thumbnailKey} />
  ),
  text: ({ resource, descriptor, thumbnailKey }) =>
    isCodeThumbnailDescriptor(descriptor) ? (
      <CodeThumbnail resource={resource} thumbnailKey={thumbnailKey} />
    ) : (
      <TextThumbnail resource={resource} thumbnailKey={thumbnailKey} />
    ),
};

export function FirstThumbnailUnit({
  resource,
  descriptor,
  thumbnailKey,
  anchor,
  onError,
}: {
  resource: ViewerResource;
  descriptor: ViewerDescriptor;
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  if (descriptor.category === "unsupported") return null;
  const render = DOCUMENT_RENDERERS[descriptor.category];
  return render({ resource, descriptor, thumbnailKey, anchor, onError });
}
components/file-thumbnail/thumbnail-cache.ts
import { registerThumbnailTestReset } from "./thumbnail-test-reset";

export interface ThumbnailCacheEntry<T> {
  promise: Promise<T>;
  status: "pending" | "resolved" | "rejected";
  value?: T;
  onResolve?: (value: T) => void;
}

export interface ThumbnailCacheStore<T> {
  get(key: string): ThumbnailCacheEntry<T> | undefined;
  set(key: string, entry: ThumbnailCacheEntry<T>): void;
  delete(key: string): boolean;
  clear?(): void;
  prune?(): void;
}

export interface ThumbnailArtifactCacheOptions<T> {
  maxEntries: number;
  dispose?: (value: T) => void;
}

export interface ThumbnailArtifactCache<T> extends ThumbnailCacheStore<T> {
  readonly size: number;
  clear(): void;
  prune(): void;
}

export function cachedThumbnailResource<T>(
  cache: ThumbnailCacheStore<T>,
  key: string,
  load: () => Promise<T>,
): Promise<T> {
  const cached = cache.get(key);
  if (cached) {
    if (cached.status === "rejected") {
      cache.delete(key);
    }
    return cached.promise;
  }

  const entry: ThumbnailCacheEntry<T> = {
    status: "pending",
    promise: load().then(
      (value) => {
        entry.status = "resolved";
        entry.value = value;
        entry.onResolve?.(value);
        cache.prune?.();
        return value;
      },
      (error) => {
        entry.status = "rejected";
        throw error;
      },
    ),
  };
  cache.set(key, entry);
  return entry.promise;
}

export function createThumbnailArtifactCache<T>({
  maxEntries,
  dispose,
}: ThumbnailArtifactCacheOptions<T>): ThumbnailArtifactCache<T> {
  const entries = new Map<string, ThumbnailCacheEntry<T>>();

  const cache: ThumbnailArtifactCache<T> = {
    get size() {
      return entries.size;
    },
    get(key) {
      const entry = entries.get(key);
      if (!entry) return undefined;
      entries.delete(key);
      entries.set(key, entry);
      return entry;
    },
    set(key, entry) {
      entries.set(key, entry);
      entry.onResolve = (value) => {
        if (entries.get(key) !== entry) dispose?.(value);
      };
      cache.prune?.();
    },
    delete(key) {
      const entry = entries.get(key);
      if (!entry) return false;
      entries.delete(key);
      disposeCacheEntry(entry, dispose);
      return true;
    },
    clear() {
      for (const entry of entries.values()) {
        disposeCacheEntry(entry, dispose);
      }
      entries.clear();
    },
    prune() {
      while (entries.size > maxEntries) {
        const evicted = evictOldestResolvedEntry(entries, dispose);
        if (!evicted) return;
      }
    },
  };

  registerThumbnailTestReset(() => cache.clear?.());

  return cache;
}

function evictOldestResolvedEntry<T>(
  entries: Map<string, ThumbnailCacheEntry<T>>,
  dispose?: (value: T) => void,
): boolean {
  for (const [key, entry] of entries) {
    if (entry.status !== "pending") {
      entries.delete(key);
      disposeCacheEntry(entry, dispose);
      return true;
    }
  }
  return false;
}

function disposeCacheEntry<T>(
  entry: ThumbnailCacheEntry<T>,
  dispose?: (value: T) => void,
) {
  if (entry.status === "resolved" && entry.value !== undefined) {
    dispose?.(entry.value);
  }
}
components/file-thumbnail/thumbnail-client-preview.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import type { ViewerDescriptor } from "@/lib/viewer-source";
import { FileThumbnailShimmer } from "@/components/ui/file-thumbnail-frame";
import { useIsClient } from "@/components/ui/use-is-client";

import { ThumbnailErrorBoundary } from "./errors";
import { FirstThumbnailUnit } from "./renderer-registry";
import { useThumbnailInView } from "./thumbnail-in-view";
import type { ThumbnailAnchor } from "./types";

export function ThumbnailClientPreview({
  resource,
  descriptor,
  thumbnailKey,
  anchor,
  onError,
}: {
  resource: ViewerResource;
  descriptor: ViewerDescriptor;
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  const isClient = useIsClient();
  const { ref: inViewRef, seen: isSeen } = useThumbnailInView();

  return (
    <div ref={inViewRef} className="absolute inset-0">
      {isClient && isSeen ? (
        <ThumbnailErrorBoundary fallback={null} onError={onError}>
          <React.Suspense fallback={<FileThumbnailShimmer />}>
            <FirstThumbnailUnit
              resource={resource}
              descriptor={descriptor}
              thumbnailKey={thumbnailKey}
              anchor={anchor}
              onError={onError}
            />
          </React.Suspense>
        </ThumbnailErrorBoundary>
      ) : (
        <FileThumbnailShimmer />
      )}
    </div>
  );
}
components/file-thumbnail/thumbnail-decode-queue.ts
import { registerThumbnailTestReset } from "./thumbnail-test-reset";

const MAX_CONCURRENT_DECODES = 3;
let activeDecodes = 0;
let decodeGeneration = 0;
const decodeQueue: Array<() => void> = [];

function acquireThumbnailDecodeSlot(): Promise<() => void> {
  return new Promise((resolve) => {
    const generation = decodeGeneration;
    const grant = () => {
      if (generation !== decodeGeneration) return;
      activeDecodes++;
      let hasReleased = false;
      resolve(() => {
        if (hasReleased) return;
        hasReleased = true;
        if (generation !== decodeGeneration) return;
        activeDecodes--;
        decodeQueue.shift()?.();
      });
    };
    if (activeDecodes < MAX_CONCURRENT_DECODES) grant();
    else decodeQueue.push(grant);
  });
}

export async function withThumbnailDecodeSlot<T>(
  fn: () => Promise<T>,
): Promise<T> {
  const release = await acquireThumbnailDecodeSlot();
  try {
    return await fn();
  } finally {
    release();
  }
}

export function getThumbnailDecodeQueueSnapshot() {
  return {
    activeDecodes,
    queuedDecodes: decodeQueue.length,
    maxConcurrentDecodes: MAX_CONCURRENT_DECODES,
  };
}

function resetThumbnailDecodeQueueForTests() {
  decodeGeneration++;
  activeDecodes = 0;
  decodeQueue.splice(0);
}

registerThumbnailTestReset(resetThumbnailDecodeQueueForTests);
components/file-thumbnail/thumbnail-direct-image.tsx
"use client";

import type { ViewerResource } from "@/lib/viewer-resource";
import type { ViewerDescriptor } from "@/lib/viewer-source";
import {
  FileThumbnailFrame,
  type FileThumbnailFrameProps,
} from "@/components/ui/file-thumbnail-frame";

import { isTiffDescriptor } from "./descriptor";
import {
  createThumbnailErrorState,
  type ThumbnailErrorState,
} from "./thumbnail-error-state";
import { createThumbnailImageLoadError } from "./thumbnail-errors";
import { ANCHOR_OBJECT_POSITION, type ThumbnailAnchor } from "./types";

export function isDirectImageThumbnail({
  descriptor,
  directUrl,
}: {
  descriptor: ViewerDescriptor;
  directUrl?: string | null;
}): boolean {
  return (
    descriptor.category === "image" &&
    !isTiffDescriptor(descriptor) &&
    Boolean(directUrl)
  );
}

export function DirectImageThumbnail({
  descriptor,
  resource,
  directUrl,
  previewAspectRatio,
  className,
  anchor,
  renderKey,
  errorState,
  thumbnailProps,
  onError,
}: {
  descriptor: ViewerDescriptor;
  resource: ViewerResource;
  directUrl: string;
  previewAspectRatio?: number;
  className?: string;
  anchor: ThumbnailAnchor;
  renderKey: string;
  errorState: ThumbnailErrorState | null;
  thumbnailProps?: Omit<FileThumbnailFrameProps, "file" | "onError">;
  onError?: (error: unknown, errorState: ThumbnailErrorState) => void;
}) {
  const failedDirectImage =
    errorState?.info.format === "image" ? errorState : null;

  return (
    <FileThumbnailFrame
      {...thumbnailProps}
      file={{ name: descriptor.displayName, type: descriptor.mimeType ?? "" }}
      previewImageUrl={directUrl}
      previewAspectRatio={previewAspectRatio}
      className={className}
      previewClassName={ANCHOR_OBJECT_POSITION[anchor]}
      state={failedDirectImage ? "error" : "loaded"}
      aria-label={failedDirectImage?.info.userMessage}
      title={failedDirectImage?.info.userMessage}
      data-error-domain={failedDirectImage?.info.domain}
      data-error-format={failedDirectImage?.info.format}
      data-error-kind={failedDirectImage?.info.kind}
      data-error-message={failedDirectImage?.info.message}
      onPreviewError={() => {
        const error = createThumbnailImageLoadError();
        const nextErrorState = createThumbnailErrorState({
          renderKey,
          error,
          resource,
          descriptor,
        });
        onError?.(error, nextErrorState);
      }}
    />
  );
}
components/file-thumbnail/thumbnail-error-state.ts
import { toViewerErrorInfo, type ViewerErrorInfo } from "@/lib/viewer-errors";
import type { ViewerResource } from "@/lib/viewer-resource";
import type { ViewerDescriptor } from "@/lib/viewer-source";

import { thumbnailCategoryFormat } from "./thumbnail-errors";

export interface ThumbnailErrorState {
  renderKey: string;
  info: ViewerErrorInfo;
}

export function createThumbnailErrorState({
  renderKey,
  error,
  resource,
  descriptor,
}: {
  renderKey: string;
  error: unknown;
  resource: ViewerResource;
  descriptor: ViewerDescriptor;
}): ThumbnailErrorState {
  return {
    renderKey,
    info: toViewerErrorInfo(error, {
      format: thumbnailCategoryFormat(descriptor.category),
      sourceKind: resource.sourceKind,
      canDownload: !resource.originalDownload.isDisabled,
    }),
  };
}
components/file-thumbnail/thumbnail-errors.ts
import {
  isResourceError,
  ViewerFormatError,
  type ViewerFormat,
} from "@/lib/viewer-errors";
import type { FileCategory } from "@/lib/viewer-source";

export async function withThumbnailFormatError<T>(
  category: FileCategory,
  kind:
    | "decode_failed"
    | "load_failed"
    | "parse_failed"
    | "render_failed"
    | "unknown",
  fileName: string,
  message: string,
  load: () => Promise<T>,
): Promise<T> {
  try {
    return await load();
  } catch (error) {
    if (isResourceError(error)) throw error;
    throw new ViewerFormatError({
      format: thumbnailCategoryFormat(category),
      kind,
      message: `${message}: ${fileName}`,
      cause: error,
    });
  }
}

export function createThumbnailImageLoadError(): ViewerFormatError {
  return new ViewerFormatError({
    format: "image",
    kind: "load_failed",
    message: "Could not load image preview.",
  });
}

export function thumbnailCategoryFormat(category: FileCategory): ViewerFormat {
  if (category === "markdown" || category === "html" || category === "email") {
    return "text";
  }
  if (category === "unsupported") return "file";
  return category;
}
components/file-thumbnail/thumbnail-in-view.ts
"use client";

import * as React from "react";
import { useKeyedMountEffect } from "@/hooks/use-keyed-mount-effect";
import { joinEffectKey } from "@/lib/effect-key";

export function useThumbnailInView() {
  const [seen, setSeen] = React.useState(false);
  const [node, setNode] = React.useState<HTMLElement | null>(null);
  const seenRef = React.useRef(false);
  const ref = React.useCallback((el: HTMLElement | null) => setNode(el), []);

  useKeyedMountEffect(joinEffectKey([node]), () => {
    if (!node || seenRef.current) return;
    if (typeof IntersectionObserver === "undefined") {
      seenRef.current = true;
      setSeen(true);
      return;
    }
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries.some((entry) => entry.isIntersecting)) {
          seenRef.current = true;
          setSeen(true);
          observer.disconnect();
        }
      },
      { rootMargin: "300px" },
    );
    observer.observe(node);
    return () => observer.disconnect();
  });

  return { ref, seen };
}
components/file-thumbnail/thumbnail-limits.ts
export const TEXT_THUMBNAIL_MAX_BYTES = 64 * 1024;
export const CSV_THUMBNAIL_MAX_ROWS = 16;
export const CSV_THUMBNAIL_MAX_COLUMNS = 6;
export const XLSX_THUMBNAIL_MAX_ROWS = 16;
export const XLSX_THUMBNAIL_MAX_COLUMNS = 6;
export const TIFF_THUMBNAIL_TARGET_WIDTH = 320;
export const TEXT_THUMBNAIL_CACHE_MAX_ENTRIES = 96;
components/file-thumbnail/thumbnail-options.ts
import type { ViewerDescriptor } from "@/lib/viewer-source";

import { isTiffDescriptor } from "./descriptor";
import { thumbnailOption, type ThumbnailOption } from "./keys";
import {
  CSV_THUMBNAIL_MAX_COLUMNS,
  CSV_THUMBNAIL_MAX_ROWS,
  TEXT_THUMBNAIL_MAX_BYTES,
  TIFF_THUMBNAIL_TARGET_WIDTH,
  XLSX_THUMBNAIL_MAX_COLUMNS,
  XLSX_THUMBNAIL_MAX_ROWS,
} from "./thumbnail-limits";

export function getThumbnailOptions(
  descriptor: ViewerDescriptor,
): ThumbnailOption[] {
  switch (descriptor.category) {
    case "text":
    case "markdown":
    case "html":
      return [thumbnailOption("text-max-bytes", TEXT_THUMBNAIL_MAX_BYTES)];
    case "csv":
      return [
        thumbnailOption("text-max-bytes", TEXT_THUMBNAIL_MAX_BYTES),
        thumbnailOption("csv-max-rows", CSV_THUMBNAIL_MAX_ROWS),
        thumbnailOption("csv-max-columns", CSV_THUMBNAIL_MAX_COLUMNS),
      ];
    case "xlsx":
      return [
        thumbnailOption("xlsx-max-rows", XLSX_THUMBNAIL_MAX_ROWS),
        thumbnailOption("xlsx-max-columns", XLSX_THUMBNAIL_MAX_COLUMNS),
      ];
    case "image":
      return isTiffDescriptor(descriptor)
        ? [thumbnailOption("tiff-target-width", TIFF_THUMBNAIL_TARGET_WIDTH)]
        : [];
    default:
      return [];
  }
}
components/file-thumbnail/thumbnail-profile.ts
import type { ThumbnailFileMeta } from "./thumbnail-text";

/**
 * Profiling helper: logs `[thumb] <label> <ms>` when enabled. Gated on a global
 * so it costs nothing in normal use; the profiler sets it before navigation.
 */
export async function timedThumbnail<T>(
  label: string,
  fn: () => Promise<T>,
): Promise<T> {
  const on =
    typeof globalThis !== "undefined" &&
    (globalThis as { __THUMB_PROFILE__?: boolean }).__THUMB_PROFILE__;
  if (!on) return fn();
  const t0 = performance.now();
  try {
    return await fn();
  } finally {
    console.log(`[thumb] ${label} ${(performance.now() - t0).toFixed(1)}ms`);
  }
}

export function shortName(meta: ThumbnailFileMeta): string {
  return meta.fileName;
}
components/file-thumbnail/thumbnail-resource.ts
import { registerThumbnailTestReset } from "./thumbnail-test-reset";

export function useThumbnailResource<T>(promise: Promise<T>): T {
  const record = getThumbnailResourceRecord(promise);
  if (record.status === "pending") throw record.promise;
  if (record.status === "rejected") throw record.error;
  return record.value as T;
}

let thumbnailResourceRecords = new WeakMap<
  Promise<unknown>,
  ThumbnailResourceRecord<unknown>
>();

function getThumbnailResourceRecord<T>(
  promise: Promise<T>,
): ThumbnailResourceRecord<T> {
  const cached = thumbnailResourceRecords.get(promise) as
    | ThumbnailResourceRecord<T>
    | undefined;
  if (cached) return cached;

  const record: ThumbnailResourceRecord<T> = {
    promise,
    status: "pending",
  };
  promise.then(
    (value) => {
      record.status = "resolved";
      record.value = value;
    },
    (error) => {
      record.status = "rejected";
      record.error = error;
    },
  );
  thumbnailResourceRecords.set(promise, record);
  return record;
}

interface ThumbnailResourceRecord<T> {
  promise: Promise<T>;
  status: "pending" | "resolved" | "rejected";
  value?: T;
  error?: unknown;
}

function resetThumbnailResourceRecordsForTests() {
  thumbnailResourceRecords = new WeakMap();
}

registerThumbnailTestReset(resetThumbnailResourceRecordsForTests);
components/file-thumbnail/thumbnail-test-reset.ts
const resetHandlers = new Set<() => void>();

export function registerThumbnailTestReset(handler: () => void): () => void {
  resetHandlers.add(handler);
  return () => {
    resetHandlers.delete(handler);
  };
}

export function clearThumbnailCachesForTests() {
  for (const reset of [...resetHandlers]) reset();
}
components/file-thumbnail/thumbnail-text.ts
import {
  isAbortError,
  isResourceError,
  ResourceError,
} from "@/lib/viewer-errors";
import type {
  ViewerContentBytes,
  ViewerContentIdentity,
  ViewerContentRange,
  ViewerContentStream,
} from "@/lib/viewer-resource";
import type { ViewerSource } from "@/lib/viewer-source";

import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "./thumbnail-cache";
import {
  TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,
  TEXT_THUMBNAIL_MAX_BYTES,
} from "./thumbnail-limits";
import { shortName, timedThumbnail } from "./thumbnail-profile";

export interface ThumbnailFileMeta {
  fileName: string;
  mimeType?: string;
  sourceKind: ViewerSource["kind"];
}

export type ThumbnailTextContent = ViewerContentIdentity &
  ViewerContentRange &
  ViewerContentStream;

export type ThumbnailBytesContent = ViewerContentIdentity & ViewerContentBytes;

const textCache = createThumbnailArtifactCache<string>({
  maxEntries: TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,
});

export function getThumbnailText(
  meta: ThumbnailFileMeta,
  content: ThumbnailTextContent,
  thumbnailKey: string,
): Promise<string> {
  return cachedThumbnailResource(textCache, thumbnailKey, () =>
    timedThumbnail(`text:fetch ${shortName(meta)}`, async () => {
      if (meta.sourceKind === "url" || content.sourceKind === "url") {
        try {
          return await readThumbnailTextRange(content, "no-store");
        } catch (error) {
          if (shouldReadTextStreamPrefix(error)) {
            return readThumbnailTextStreamPrefix(content, "no-store");
          }
          throw error;
        }
      }
      return readThumbnailTextRange(content);
    }),
  );
}

export function thumbnailFileMeta({
  fileName,
  mimeType,
  sourceKind,
}: ThumbnailFileMeta): ThumbnailFileMeta {
  return { fileName, mimeType, sourceKind };
}

function shouldReadTextStreamPrefix(error: unknown): boolean {
  const candidate = resourceErrorCandidate(error);
  return Boolean(
    candidate &&
      (candidate.kind === "invalid_range" ||
        (candidate.kind === "http_error" && candidate.status === 416)),
  );
}

function resourceErrorCandidate(error: unknown):
  | {
      kind?: unknown;
      status?: unknown;
    }
  | undefined {
  if (!error || typeof error !== "object") return undefined;
  return error as { kind?: unknown; status?: unknown };
}

async function readThumbnailTextRange(
  content: ThumbnailTextContent,
  cache?: RequestCache,
) {
  const rangeRequest = {
    start: 0,
    end: TEXT_THUMBNAIL_MAX_BYTES - 1,
  };
  const range = cache
    ? await content.readRange(rangeRequest, { cache })
    : await content.readRange(rangeRequest);
  return new TextDecoder().decode(range.buffer);
}

async function readThumbnailTextStreamPrefix(
  content: ThumbnailTextContent,
  cache?: RequestCache,
) {
  const stream = cache
    ? await content.readStream({ cache })
    : await content.readStream();
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let remainingBytes = TEXT_THUMBNAIL_MAX_BYTES;
  let text = "";

  try {
    while (remainingBytes > 0) {
      const { done, value } = await reader.read();
      if (done) return text + decoder.decode();

      const chunk =
        value.byteLength > remainingBytes
          ? value.slice(0, remainingBytes)
          : value;
      text += decoder.decode(chunk, { stream: true });
      remainingBytes -= chunk.byteLength;

      if (chunk.byteLength < value.byteLength || remainingBytes === 0) {
        cancelThumbnailTextReader(reader);
        return text + decoder.decode();
      }
    }

    cancelThumbnailTextReader(reader);
    return text + decoder.decode();
  } catch (error) {
    throw createThumbnailTextReadError(error);
  }
}

function createThumbnailTextReadError(error: unknown): ResourceError {
  if (isResourceError(error)) return error;
  if (isAbortError(error)) {
    return new ResourceError({
      kind: "aborted",
      message: "Resource load was aborted.",
      cause: error,
    });
  }
  return new ResourceError({
    kind: "fetch_failed",
    message: "Could not read this resource.",
    cause: error,
  });
}

function cancelThumbnailTextReader(
  reader: ReadableStreamDefaultReader<Uint8Array>,
) {
  try {
    void reader.cancel().catch(() => {});
  } catch {
    /* The preview prefix is already available; cancellation is best-effort. */
  }
}
components/file-thumbnail/thumbnail-worker-client.ts
import { registerThumbnailTestReset } from "./thumbnail-test-reset";

export interface ThumbnailWorkerMessage {
  id: number;
}

export interface ThumbnailWorkerClientOptions<Request, Response> {
  createWorker: () => Worker;
  resolve: (response: Response) => unknown;
  reject: (response: Response) => string | Error;
}

export interface ThumbnailWorkerRequest<Request> {
  request: Request;
  transfer?: Transferable[];
}

export interface ThumbnailWorkerClient<Request, Response> {
  request<T = unknown>(input: ThumbnailWorkerRequest<Request>): Promise<T>;
  reset(): void;
  pendingCount(): number;
}

export function createThumbnailWorkerClient<
  Request extends ThumbnailWorkerMessage,
  Response extends ThumbnailWorkerMessage,
>({
  createWorker,
  resolve,
  reject,
}: ThumbnailWorkerClientOptions<Request, Response>): ThumbnailWorkerClient<
  Omit<Request, "id">,
  Response
> {
  let worker: Worker | null = null;
  let requestId = 0;
  const pending = new Map<
    number,
    { resolve: (value: unknown) => void; reject: (error: Error) => void }
  >();

  const getWorker = () => {
    if (!worker) {
      const nextWorker = createWorker();
      worker = nextWorker;
      nextWorker.onmessage = (event: MessageEvent<Response>) => {
        if (worker !== nextWorker) return;
        const response = event.data;
        const entry = pending.get(response.id);
        if (!entry) return;
        pending.delete(response.id);
        try {
          const value = resolve(response);
          if (value !== undefined) entry.resolve(value);
          else entry.reject(toWorkerError(reject(response)));
        } catch (error) {
          entry.reject(toWorkerError(error));
        }
      };
      nextWorker.onerror = (event) => {
        if (worker !== nextWorker) return;
        nextWorker.terminate();
        worker = null;
        requestId = 0;
        rejectPendingRequests(
          event.error instanceof Error
            ? event.error
            : new Error(event.message || "Thumbnail worker failed."),
        );
      };
    }
    return worker;
  };

  const rejectPendingRequests = (error: Error) => {
    for (const entry of pending.values()) entry.reject(error);
    pending.clear();
  };

  const reset = () => {
    worker?.terminate();
    worker = null;
    requestId = 0;
    rejectPendingRequests(new Error("Thumbnail worker reset."));
  };

  const client: ThumbnailWorkerClient<Omit<Request, "id">, Response> = {
    request<T = unknown>(
      input: ThumbnailWorkerRequest<Omit<Request, "id">>,
    ): Promise<T> {
      const { request, transfer } = input;
      const id = ++requestId;
      return new Promise<T>((resolveRequest, rejectRequest) => {
        pending.set(id, {
          resolve: (value) => resolveRequest(value as T),
          reject: rejectRequest,
        });
        try {
          getWorker().postMessage({ id, ...request }, transfer ?? []);
        } catch (error) {
          pending.delete(id);
          rejectRequest(toWorkerError(error));
        }
      });
    },
    reset,
    pendingCount: () => pending.size,
  };

  registerThumbnailTestReset(reset);

  return client;
}

function toWorkerError(error: unknown): Error {
  if (error instanceof Error) return error;
  return new Error(String(error || "Thumbnail worker failed."));
}
components/file-thumbnail/types.ts
/**
 * Which corner of a generated preview stays pinned when its content is larger
 * than the thumbnail frame.
 */
export type ThumbnailAnchor = "top-left" | "top-right" | "bottom-left";

/** Absolute-position classes that pin a covered element to its anchor corner. */
export const ANCHOR_CORNER: Record<ThumbnailAnchor, string> = {
  "top-left": "top-0 left-0",
  "top-right": "top-0 right-0",
  "bottom-left": "bottom-0 left-0",
};

/** `object-position` classes for the <img> cover path (raster images). */
export const ANCHOR_OBJECT_POSITION: Record<ThumbnailAnchor, string> = {
  "top-left": "object-left-top",
  "top-right": "object-right-top",
  "bottom-left": "object-left-bottom",
};
components/file-thumbnail/renderers/csv-thumbnail.tsx
"use client";

import * as React from "react";

import { inferCsvDialect, parseCsv } from "@/lib/csv";
import type { ViewerResource } from "@/lib/viewer-resource";
import { GridTable } from "@/components/file-thumbnail/renderers/layout";
import {
  CSV_THUMBNAIL_MAX_COLUMNS,
  CSV_THUMBNAIL_MAX_ROWS,
} from "@/components/file-thumbnail/thumbnail-limits";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  getThumbnailText,
  thumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";

export function CsvFirstRows({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const raw = useThumbnailResource(
    getThumbnailText(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  const rows = React.useMemo(() => {
    const dialect = inferCsvDialect({
      fileName: resource.fileName,
      mimeType: resource.mimeType,
    });
    const table = parseCsv(raw, dialect);
    const header = table.columns.length
      ? [table.columns.slice(0, CSV_THUMBNAIL_MAX_COLUMNS)]
      : [];
    return header.concat(
      table.rows
        .slice(0, CSV_THUMBNAIL_MAX_ROWS - header.length)
        .map((row) => row.slice(0, CSV_THUMBNAIL_MAX_COLUMNS)),
    );
  }, [raw, resource.fileName, resource.mimeType]);
  return <GridTable rows={rows} headerRow />;
}
components/file-thumbnail/renderers/docx-thumbnail.tsx
"use client";

import * as React from "react";

import { getDocxDocumentResource } from "@/lib/docx-document-resource";
import type { ViewerResource } from "@/lib/viewer-resource";
import { renderCachedDocxPreview } from "@/components/ui/docx-viewer-render";
import {
  Surface,
  useElementWidth,
} from "@/components/file-thumbnail/renderers/layout";
import { withThumbnailDecodeSlot } from "@/components/file-thumbnail/thumbnail-decode-queue";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";

const DOCX_PAGE_W = 816; // US Letter at 96dpi
const DOCX_THUMBNAIL_PAGE_STYLE = {
  backgroundColor: "white",
  color: "black",
  colorScheme: "light",
} satisfies React.CSSProperties;

export function DocxFirstPage({ resource }: { resource: ViewerResource }) {
  const bytes = useThumbnailResource(getDocxDocumentResource(resource.content));
  const { ref: frameRef, width: frameWidth } = useElementWidth();
  const [renderError, setRenderError] = React.useState<unknown>(null);

  const renderRef = React.useCallback(
    (el: HTMLDivElement | null) => {
      if (!el) return;
      let active = true;
      void withThumbnailDecodeSlot(() =>
        withThumbnailFormatError(
          "docx",
          "render_failed",
          resource.fileName,
          "Failed to render DOCX thumbnail",
          () =>
            timedThumbnail(`docx:render ${shortName(resource)}`, async () => {
              if (!active) return;
              const { renderHost } = await renderCachedDocxPreview({
                buffer: () => bytes.slice(0),
                cacheKey: resource.content.key,
                getScale: () => 1,
              });
              if (!active) return;
              commitDocxThumbnailRender(el, renderHost);
            }),
        ),
      ).catch((error) => {
        if (active) setRenderError(error);
      });
      return () => {
        active = false;
      };
    },
    [bytes, resource],
  );

  const scale = frameWidth ? frameWidth / DOCX_PAGE_W : null;

  if (renderError) throw renderError;

  return (
    <Surface>
      <div ref={frameRef} className="absolute inset-0 overflow-hidden bg-white">
        <div
          className="absolute top-0 left-0 origin-top-left [&_.docx-wrapper]:!bg-transparent [&_.docx-wrapper]:!p-0 [&_section.docx]:!mb-0 [&_section.docx]:!bg-white [&_section.docx]:!text-black [&_section.docx]:!shadow-none"
          style={{
            ...DOCX_THUMBNAIL_PAGE_STYLE,
            width: DOCX_PAGE_W,
            transform: scale ? `scale(${scale})` : undefined,
            visibility: scale ? "visible" : "hidden",
          }}
        >
          <div ref={renderRef} />
        </div>
      </div>
    </Surface>
  );
}

function commitDocxThumbnailRender(host: HTMLElement, renderHost: HTMLElement) {
  const wrapper = renderHost.querySelector<HTMLElement>(".docx-wrapper");
  const firstPage = renderHost.querySelector<HTMLElement>(
    ".docx-wrapper > section.docx",
  );
  if (!wrapper || !firstPage) {
    throw new Error("DOCX render produced no pages.");
  }

  const staticNodes = Array.from(renderHost.childNodes).filter(
    (node) => node !== wrapper,
  );
  wrapper.replaceChildren(firstPage);
  host.replaceChildren(...staticNodes, wrapper);
}
components/file-thumbnail/renderers/html-thumbnail.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import { IframeDoc } from "@/components/file-thumbnail/renderers/layout";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  getThumbnailText,
  thumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";

export function HtmlFirstPage({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const html = useThumbnailResource(
    getThumbnailText(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  return <IframeDoc html={html} />;
}
components/file-thumbnail/renderers/image-thumbnail.tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import type { ViewerContentBlob, ViewerResource } from "@/lib/viewer-resource";
import { FileThumbnailShimmer } from "@/components/ui/file-thumbnail-frame";
import { useObjectUrl } from "@/components/file-thumbnail/renderers/use-object-url";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { createThumbnailImageLoadError } from "@/components/file-thumbnail/thumbnail-errors";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import type { ThumbnailAnchor } from "@/components/file-thumbnail/types";
import { ANCHOR_CORNER } from "@/components/file-thumbnail/types";

const IMAGE_BLOB_THUMBNAIL_CACHE_MAX_ENTRIES = 96;

const imageBlobCache = createThumbnailArtifactCache<Blob>({
  maxEntries: IMAGE_BLOB_THUMBNAIL_CACHE_MAX_ENTRIES,
});

function getImageBlob(content: ViewerContentBlob): Promise<Blob> {
  return cachedThumbnailResource(imageBlobCache, content.key, () =>
    content.readBlob(),
  );
}

export function ImageFirstFrame({
  resource,
  anchor,
  onError,
}: {
  resource: ViewerResource;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  const directUrl = resource.content.directUrl;
  if (directUrl) {
    return (
      <ImageUrlPreview url={directUrl} anchor={anchor} onError={onError} />
    );
  }

  return (
    <ImageBlobPreview
      content={resource.content}
      anchor={anchor}
      onError={onError}
    />
  );
}

function ImageUrlPreview({
  url,
  anchor,
  onError,
}: {
  url: string;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  return (
    <div className="absolute inset-0 overflow-hidden bg-white">
      <img
        src={url}
        alt=""
        loading="lazy"
        decoding="async"
        className={cn("absolute block w-full", ANCHOR_CORNER[anchor])}
        onError={() => onError(createThumbnailImageLoadError())}
      />
    </div>
  );
}

function ImageBlobPreview({
  content,
  anchor,
  onError,
}: {
  content: ViewerContentBlob;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  const blob = useThumbnailResource(getImageBlob(content));
  const url = useObjectUrl(blob);

  if (!url) return <FileThumbnailShimmer />;

  return <ImageUrlPreview url={url} anchor={anchor} onError={onError} />;
}
components/file-thumbnail/renderers/layout.tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";

/** Centered, clipped white surface that hosts a rendered first unit. */
export function Surface({ children }: { children: React.ReactNode }) {
  return (
    <div className="absolute inset-0 flex items-center justify-center overflow-hidden bg-white">
      {children}
    </div>
  );
}

export function useElementWidth() {
  const [width, setWidth] = React.useState<number | null>(null);
  const ref = React.useCallback((el: HTMLElement | null) => {
    if (!el) return;
    setWidth(el.clientWidth);
    const observer = new ResizeObserver((entries) => {
      const nextWidth = entries[0]?.contentRect.width;
      if (nextWidth) setWidth(nextWidth);
    });
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  return { ref, width };
}

const GRID_COL_W = 46;

/** Internal-gridline table shared by the XLSX and CSV previews. */
export function GridTable({
  rows,
  headerRow,
}: {
  rows: string[][];
  headerRow?: boolean;
}) {
  const colCount = Math.max(1, ...rows.map((r) => r.length));
  return (
    <div className="absolute inset-0 overflow-hidden bg-white text-slate-700 dark:bg-slate-950 dark:text-slate-300">
      <table
        className="border-collapse leading-tight"
        style={{
          width: colCount * GRID_COL_W,
          tableLayout: "fixed",
          fontSize: 7,
        }}
      >
        <tbody>
          {rows.map((row, r) => (
            <tr key={r}>
              {Array.from({ length: colCount }, (_, c) => (
                <td
                  key={c}
                  className={cn(
                    "truncate border-r border-b border-slate-200 px-1 py-0.5 last:border-r-0 dark:border-slate-800",
                    headerRow && r === 0
                      ? "bg-slate-50 font-semibold text-slate-900 dark:bg-slate-900/70 dark:text-slate-100"
                      : "text-slate-700 dark:text-slate-300",
                  )}
                >
                  {row[c] ?? ""}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/**
 * Renders an HTML string into a fixed-size (square) sandboxed iframe and scales
 * it to the measured frame width, so the top of the page fills the tile. The
 * iframe is inert (no scripts, no pointer events).
 */
export function IframeDoc({ html }: { html: string }) {
  const { ref, width } = useElementWidth();

  const BASE = 820;
  const scale = width ? width / BASE : null;

  return (
    <div ref={ref} className="absolute inset-0 overflow-hidden bg-white">
      <iframe
        srcDoc={html}
        title=""
        sandbox=""
        scrolling="no"
        tabIndex={-1}
        aria-hidden
        className="pointer-events-none origin-top-left border-0"
        style={{
          width: BASE,
          height: BASE,
          transform: scale ? `scale(${scale})` : undefined,
          visibility: scale ? "visible" : "hidden",
        }}
      />
    </div>
  );
}
components/file-thumbnail/renderers/markdown-thumbnail.tsx
"use client";

import * as React from "react";
import type * as DOMPurify from "dompurify";
import type * as Marked from "marked";

import type { ViewerResource } from "@/lib/viewer-resource";
import { IframeDoc } from "@/components/file-thumbnail/renderers/layout";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  getThumbnailText,
  thumbnailFileMeta,
  type ThumbnailFileMeta,
  type ThumbnailTextContent,
} from "@/components/file-thumbnail/thumbnail-text";

let mdLibs: Promise<[typeof Marked, typeof DOMPurify]> | null = null;

function loadMarkdown() {
  if (!mdLibs) mdLibs = Promise.all([import("marked"), import("dompurify")]);
  return mdLibs;
}

const MARKDOWN_THUMBNAIL_CACHE_MAX_ENTRIES = 64;

const markdownBodyCache = createThumbnailArtifactCache<string>({
  maxEntries: MARKDOWN_THUMBNAIL_CACHE_MAX_ENTRIES,
});

type MarkdownThumbnailColorScheme = "light" | "dark";

function getMarkdownBody(
  meta: ThumbnailFileMeta,
  content: ThumbnailTextContent,
  thumbnailKey: string,
): Promise<string> {
  return cachedThumbnailResource(markdownBodyCache, thumbnailKey, () =>
    withThumbnailFormatError(
      "markdown",
      "render_failed",
      meta.fileName,
      "Failed to render markdown thumbnail",
      () =>
        timedThumbnail(`markdown:total ${shortName(meta)}`, async () => {
          const [text, [{ marked }, DOMPurifyMod]] = await Promise.all([
            getThumbnailText(meta, content, thumbnailKey),
            loadMarkdown(),
          ]);
          const purifier = DOMPurifyMod as unknown as {
            default?: { sanitize?: (html: string) => string };
            sanitize?: (html: string) => string;
          };
          const sanitize = purifier.default?.sanitize ?? purifier.sanitize;
          if (!sanitize) throw new Error("DOMPurify sanitize unavailable");
          return sanitize(await marked.parse(text));
        }),
    ),
  );
}

function createMarkdownDoc(
  body: string,
  colorScheme: MarkdownThumbnailColorScheme,
) {
  return `<!doctype html><html data-color-scheme="${colorScheme}"><head><meta charset="utf-8"><style>
        :root{color-scheme:light;--md-bg:#ffffff;--md-fg:#0f172a;--md-muted:#f1f5f9;--md-border:#e2e8f0;--md-quote:#475569;--md-link:#4f46e5}
        :root[data-color-scheme="dark"]{color-scheme:dark;--md-bg:#111113;--md-fg:#e4e4e7;--md-muted:#1f2937;--md-border:#3f3f46;--md-quote:#a1a1aa;--md-link:#a5b4fc}
        html,body{min-height:100%;background:var(--md-bg)}
        body{margin:0;padding:18px;font:14px/1.6 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;color:var(--md-fg)}
        h1{font-size:1.6em;margin:.1em 0 .5em;border-bottom:1px solid var(--md-border);padding-bottom:.25em}
        h2{font-size:1.3em;margin:1em 0 .4em;border-bottom:1px solid var(--md-border);padding-bottom:.25em}
        h3{font-size:1.1em;margin:1em 0 .3em}
        p,ul,ol{margin:0 0 .8em}ul,ol{padding-left:1.4em}
        code{font-family:ui-monospace,SFMono-Regular,monospace;background:var(--md-muted);padding:.1em .35em;border-radius:4px;font-size:.85em}
        pre{background:var(--md-muted);padding:12px;border-radius:8px;overflow:hidden}pre code{background:none;padding:0}
        table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--md-border);padding:4px 8px;text-align:left}
        a{color:var(--md-link)}blockquote{margin:0 0 .8em;padding-left:12px;border-left:3px solid var(--md-border);color:var(--md-quote)}
      </style></head><body>${body}</body></html>`;
}

function useMarkdownThumbnailColorScheme(): MarkdownThumbnailColorScheme {
  return React.useSyncExternalStore(
    subscribeMarkdownThumbnailColorScheme,
    getMarkdownThumbnailColorScheme,
    () => "light",
  );
}

function subscribeMarkdownThumbnailColorScheme(onChange: () => void) {
  if (
    typeof document === "undefined" ||
    typeof MutationObserver === "undefined"
  ) {
    return () => {};
  }

  const observer = new MutationObserver(onChange);
  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ["class"],
  });
  return () => observer.disconnect();
}

function getMarkdownThumbnailColorScheme(): MarkdownThumbnailColorScheme {
  if (
    typeof document !== "undefined" &&
    document.documentElement.classList.contains("dark")
  ) {
    return "dark";
  }
  return "light";
}

export function MarkdownFirstPage({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const colorScheme = useMarkdownThumbnailColorScheme();
  const body = useThumbnailResource(
    getMarkdownBody(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  return <IframeDoc html={createMarkdownDoc(body, colorScheme)} />;
}
components/file-thumbnail/renderers/msg-thumbnail.tsx
"use client";

import * as React from "react";
import type MsgReader from "@kenjiuno/msgreader";
import type { FieldsData } from "@kenjiuno/msgreader";

import type {
  ViewerContentBytes,
  ViewerContentIdentity,
  ViewerResource,
} from "@/lib/viewer-resource";
import { IframeDoc } from "@/components/file-thumbnail/renderers/layout";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import {
  TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,
  TEXT_THUMBNAIL_MAX_BYTES,
} from "@/components/file-thumbnail/thumbnail-limits";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";

type MsgReaderConstructor = new (
  arrayBuffer: ArrayBuffer | DataView,
) => MsgReader;
type MsgThumbnailContent = ViewerContentIdentity & ViewerContentBytes;

let msgReaderLib: Promise<{ default: MsgReaderConstructor }> | null = null;

const msgHtmlCache = createThumbnailArtifactCache<string>({
  maxEntries: TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,
});

function loadMsgReader() {
  if (!msgReaderLib) {
    msgReaderLib = import("@kenjiuno/msgreader") as Promise<{
      default: MsgReaderConstructor;
    }>;
  }
  return msgReaderLib;
}

export function MsgEmailThumbnail({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const html = useThumbnailResource(
    getMsgThumbnailHtml(resource, resource.content, thumbnailKey),
  );

  return <IframeDoc html={html} />;
}

function getMsgThumbnailHtml(
  meta: Pick<ViewerResource, "fileName" | "sourceKind">,
  content: MsgThumbnailContent,
  thumbnailKey: string,
): Promise<string> {
  return cachedThumbnailResource(msgHtmlCache, thumbnailKey, () =>
    withThumbnailFormatError(
      "email",
      "parse_failed",
      meta.fileName,
      "Failed to parse MSG thumbnail",
      () =>
        timedThumbnail(`msg:parse ${shortName(meta)}`, async () => {
          const bytes = await content.readBytes();
          const { default: MsgReaderClass } = await loadMsgReader();
          const reader = new MsgReaderClass(bytes.slice(0));
          const fields = reader.getFileData();
          if (fields.error) throw new Error(fields.error);
          return msgFieldsToHtml(fields);
        }),
    ),
  );
}

export function msgFieldsToHtml(
  fields: Pick<FieldsData, "body" | "bodyHtml" | "html">,
) {
  const bodyHtml = cleanMsgHtml(fields.bodyHtml);
  if (bodyHtml) return bodyHtml;

  if (fields.html?.byteLength) {
    const html = cleanMsgHtml(decodeMsgHtmlBytes(fields.html));
    if (html) return html;
  }

  const body = fields.body?.trim();
  if (body) return plainTextEmailHtml(body);

  throw new Error("MSG file does not contain a previewable email body.");
}

export function decodeMsgHtmlBytes(bytes: Uint8Array) {
  if (bytes[0] === 0xff && bytes[1] === 0xfe) {
    return new TextDecoder("utf-16le").decode(bytes.subarray(2));
  }
  if (bytes[0] === 0xfe && bytes[1] === 0xff) {
    return new TextDecoder("utf-16be").decode(bytes.subarray(2));
  }
  if (looksLikeUtf16Le(bytes)) return new TextDecoder("utf-16le").decode(bytes);
  return new TextDecoder("utf-8").decode(bytes);
}

function looksLikeUtf16Le(bytes: Uint8Array) {
  const sampleLength = Math.min(bytes.byteLength, TEXT_THUMBNAIL_MAX_BYTES);
  if (sampleLength < 4) return false;

  let oddNulls = 0;
  let evenNulls = 0;
  for (let index = 0; index < sampleLength; index += 1) {
    if (bytes[index] !== 0) continue;
    if (index % 2 === 0) evenNulls += 1;
    else oddNulls += 1;
  }

  return oddNulls > evenNulls * 4 && oddNulls > sampleLength / 8;
}

function cleanMsgHtml(html: string | null | undefined) {
  return html?.replace(/\0+$/g, "").trim() ?? "";
}

function plainTextEmailHtml(text: string) {
  return `<!doctype html><html><head><meta charset="utf-8" /></head><body><pre style="margin:0;white-space:pre-wrap;font:14px/1.5 system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#111827;">${escapeHtml(text)}</pre></body></html>`;
}

function escapeHtml(text: string) {
  return text
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}
components/file-thumbnail/renderers/pdf-thumbnail.tsx
"use client";

import * as React from "react";

import type { PdfDocumentProxy } from "@/lib/pdf-document-types";
import {
  getPdfDocumentResource,
  getPdfPageResource,
} from "@/lib/pdf-document-resource";
import { cn } from "@/lib/utils";
import type { ViewerResource } from "@/lib/viewer-resource";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import type { ThumbnailAnchor } from "@/components/file-thumbnail/types";
import { ANCHOR_CORNER } from "@/components/file-thumbnail/types";
import { getPdfCanvasPixelSize } from "@/components/ui/pdf-viewer-canvas";
import {
  readPdfRenderedPageCache,
  usePdfRenderedPageCache,
  writePdfRenderedPageCache,
  type PdfRenderedPageSignature,
} from "@/components/ui/pdf-viewer-render-cache";

const PDF_FIRST_PAGE_THUMBNAIL_WIDTH = 520;

// Page 1 via pdfjs, reusing the PdfViewer's cached document.
export function PdfFirstPage({
  resource,
  anchor,
}: {
  resource: ViewerResource;
  anchor: ThumbnailAnchor;
}) {
  const doc = useThumbnailResource(
    getPdfDocumentResource(resource.content),
  ) as PdfDocumentProxy;
  const renderCache = usePdfRenderedPageCache(doc);
  const page = useThumbnailResource(getPdfPageResource(doc, 1));
  const [renderError, setRenderError] = React.useState<unknown>(null);

  const baseViewport = React.useMemo(
    () => page.getViewport({ scale: 1 }),
    [page],
  );
  const viewport = React.useMemo(() => {
    return page.getViewport({
      scale: PDF_FIRST_PAGE_THUMBNAIL_WIDTH / baseViewport.width,
    });
  }, [baseViewport.width, page]);
  const dpr =
    (typeof window !== "undefined" ? window.devicePixelRatio : 1) || 1;

  if (renderError) throw renderError;

  const canvasRef = React.useCallback(
    (canvas: HTMLCanvasElement | null) => {
      if (!canvas) return;
      const context = canvas.getContext("2d");
      if (!context) return;
      const renderSignature = {
        documentKey: resource.content.key,
        pageNumber: 1,
        scale: PDF_FIRST_PAGE_THUMBNAIL_WIDTH / baseViewport.width,
        rotation: 0,
        devicePixelRatio: dpr,
        viewportWidth: viewport.width,
        viewportHeight: viewport.height,
      } satisfies PdfRenderedPageSignature;
      const canvasWidth = getPdfCanvasPixelSize(viewport.width, dpr);
      const canvasHeight = getPdfCanvasPixelSize(viewport.height, dpr);
      const cachedPage = readPdfRenderedPageCache(renderCache, renderSignature);

      resizeCanvas(canvas, canvasWidth, canvasHeight);
      if (cachedPage) {
        drawCanvasImage(context, cachedPage.canvas, canvasWidth, canvasHeight);
        return;
      }

      let active = true;
      let didFinishPdfRender = false;
      let task: ReturnType<typeof page.render>;
      try {
        task = page.render({
          canvas,
          canvasContext: context,
          viewport,
          transform: dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : undefined,
        });
      } catch (error) {
        void normalizePdfThumbnailRenderError(
          resource.fileName,
          Promise.reject(error),
        ).catch((normalizedError: unknown) => {
          if (active) setRenderError(normalizedError);
        });
        return () => {
          active = false;
        };
      }
      void normalizePdfThumbnailRenderError(
        resource.fileName,
        task.promise,
      ).then(
        () => {
          if (!active) return;
          didFinishPdfRender = true;
          writePdfRenderedPageCache({
            cache: renderCache,
            rendered: renderSignature,
            sourceCanvas: canvas,
          });
        },
        (error: unknown) => {
          didFinishPdfRender = true;
          if (active) setRenderError(error);
        },
      );
      return () => {
        active = false;
        if (!didFinishPdfRender) task.cancel();
      };
    },
    [
      baseViewport.width,
      dpr,
      page,
      renderCache,
      resource.content.key,
      resource.fileName,
      viewport,
    ],
  );

  return (
    <div className="absolute inset-0 overflow-hidden bg-white">
      <canvas
        ref={canvasRef}
        className={cn("absolute block w-full", ANCHOR_CORNER[anchor])}
      />
    </div>
  );
}

function normalizePdfThumbnailRenderError(
  fileName: string,
  promise: Promise<void>,
) {
  return withThumbnailFormatError(
    "pdf",
    "render_failed",
    fileName,
    "Failed to render PDF thumbnail",
    () => promise,
  );
}

function resizeCanvas(
  canvas: HTMLCanvasElement,
  width: number,
  height: number,
) {
  if (canvas.width !== width) canvas.width = width;
  if (canvas.height !== height) canvas.height = height;
}

function drawCanvasImage(
  context: CanvasRenderingContext2D,
  image: HTMLCanvasElement,
  width: number,
  height: number,
) {
  if (typeof context.setTransform === "function") {
    context.setTransform(1, 0, 0, 1, 0, 0);
  }
  if (typeof context.drawImage === "function") {
    context.drawImage(image, 0, 0, width, height);
  }
}
components/file-thumbnail/renderers/pptx-thumbnail.tsx
"use client";

import * as React from "react";
import { useMountEffect } from "@/hooks/use-mount-effect";

import { cn } from "@/lib/utils";
import type { ViewerResource } from "@/lib/viewer-resource";
import { getPptxSource } from "@/components/ui/pptx-viewer-source";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { withThumbnailDecodeSlot } from "@/components/file-thumbnail/thumbnail-decode-queue";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  thumbnailFileMeta,
  type ThumbnailBytesContent,
  type ThumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";
import type { ThumbnailAnchor } from "@/components/file-thumbnail/types";
import { ANCHOR_CORNER } from "@/components/file-thumbnail/types";

interface PptxFirstSlideSource {
  render: (canvas: HTMLCanvasElement, scale: number) => Promise<void>;
  baseWidth: number;
  baseHeight: number;
  dispose?: () => void;
}

const pptxCache = createThumbnailArtifactCache<PptxFirstSlideSource>({
  maxEntries: 4,
  dispose: (source) => source.dispose?.(),
});

function getPptxFirstSlide(
  meta: ThumbnailFileMeta,
  content: ThumbnailBytesContent,
  thumbnailKey: string,
): Promise<PptxFirstSlideSource> {
  return cachedThumbnailResource(pptxCache, thumbnailKey, () =>
    withThumbnailDecodeSlot(() =>
      withThumbnailFormatError(
        "pptx",
        "parse_failed",
        meta.fileName,
        "Failed to parse presentation thumbnail",
        () =>
          timedThumbnail(`pptx:total ${shortName(meta)}`, async () => {
            const source = await getPptxSource(content);
            const release = source.retain();
            const render = async (canvas: HTMLCanvasElement, scale: number) => {
              await withThumbnailFormatError(
                "pptx",
                "render_failed",
                meta.fileName,
                "Failed to render presentation thumbnail",
                async () => {
                  const result = await source.renderSlide({
                    canvas,
                    renderScale: scale,
                    slideIndex: 0,
                    priority: {
                      distanceFromReadingMarker: 0,
                      isCurrentSlide: true,
                      isInViewport: true,
                      isScrollLead: true,
                    },
                  });
                  if (result.status === "failed") throw result.error;
                },
              );
            };
            return {
              render,
              baseWidth: source.baseSize.width,
              baseHeight: source.baseSize.height,
              dispose: release,
            };
          }),
      ),
    ),
  );
}

export function PptxFirstSlide({
  resource,
  thumbnailKey,
  anchor,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
}) {
  useMountEffect(() => {
    const preload = () => {
      void import("@/components/ui/pptx-viewer").then((module) => {
        module.preloadPptxViewer();
      });
    };
    if (typeof window.requestIdleCallback === "function") {
      const id = window.requestIdleCallback(preload);
      return () => window.cancelIdleCallback(id);
    }
    const id = window.setTimeout(preload, 0);
    return () => window.clearTimeout(id);
  });

  const source = useThumbnailResource(
    getPptxFirstSlide(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  const [renderError, setRenderError] = React.useState<unknown>(null);
  const baseW = source.baseWidth || 960;
  const baseH = source.baseHeight || 720;
  const FILL_PX = 1024;
  const scale = FILL_PX / Math.min(baseW, baseH);

  if (renderError) throw renderError;

  const canvasRef = React.useCallback(
    (canvas: HTMLCanvasElement | null) => {
      if (!canvas) return;
      let active = true;
      source.render(canvas, scale).catch((error: unknown) => {
        if (active) setRenderError(error);
      });
      return () => {
        active = false;
      };
    },
    [source, scale],
  );

  const landscape = baseW >= baseH;
  return (
    <div className="absolute inset-0 overflow-hidden bg-white">
      <canvas
        ref={canvasRef}
        className={cn(
          "absolute block",
          ANCHOR_CORNER[anchor],
          landscape ? "h-full w-auto max-w-none" : "h-auto w-full",
        )}
        style={{ aspectRatio: `${baseW} / ${baseH}` }}
      />
    </div>
  );
}
components/file-thumbnail/renderers/text-thumbnail.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  getThumbnailText,
  thumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";

const TEXT_THUMBNAIL_MAX_PARAGRAPHS = 12;
const TEXT_THUMBNAIL_FONT_SIZE = 7;
const TEXT_THUMBNAIL_LINE_HEIGHT = 1.55;

export function TextThumbnail({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const raw = useThumbnailResource(
    getThumbnailText(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  const paragraphs = React.useMemo(() => proseThumbnailParagraphs(raw), [raw]);

  if (!paragraphs.length) return <EmptyTextThumbnail />;

  return (
    <div
      data-slot="text-thumbnail"
      className="absolute inset-0 overflow-hidden bg-white p-3 font-sans text-slate-700 dark:bg-slate-950 dark:text-slate-300"
      style={{
        fontSize: TEXT_THUMBNAIL_FONT_SIZE,
        lineHeight: TEXT_THUMBNAIL_LINE_HEIGHT,
      }}
    >
      <div className="space-y-1.5">
        {paragraphs.map((paragraph, i) => (
          <p key={i} className="text-pretty break-words">
            {paragraph}
          </p>
        ))}
      </div>
    </div>
  );
}

function EmptyTextThumbnail() {
  return (
    <div
      aria-label="Empty text file"
      data-slot="text-thumbnail-empty"
      className="absolute inset-0 bg-white dark:bg-slate-950"
    />
  );
}

function proseThumbnailParagraphs(text: string) {
  return text
    .replace(/\r\n?/g, "\n")
    .trim()
    .split(/\n{2,}/)
    .map((paragraph) => paragraph.replace(/\s*\n\s*/g, " ").trim())
    .filter(Boolean)
    .slice(0, TEXT_THUMBNAIL_MAX_PARAGRAPHS);
}
components/file-thumbnail/renderers/tiff-thumbnail.tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import type { ViewerResource } from "@/lib/viewer-resource";
import { FileThumbnailShimmer } from "@/components/ui/file-thumbnail-frame";
import { useObjectUrl } from "@/components/file-thumbnail/renderers/use-object-url";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { withThumbnailDecodeSlot } from "@/components/file-thumbnail/thumbnail-decode-queue";
import {
  createThumbnailImageLoadError,
  withThumbnailFormatError,
} from "@/components/file-thumbnail/thumbnail-errors";
import { TIFF_THUMBNAIL_TARGET_WIDTH } from "@/components/file-thumbnail/thumbnail-limits";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  thumbnailFileMeta,
  type ThumbnailBytesContent,
  type ThumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";
import {
  createThumbnailWorkerClient,
  type ThumbnailWorkerMessage,
} from "@/components/file-thumbnail/thumbnail-worker-client";
import type { ThumbnailAnchor } from "@/components/file-thumbnail/types";
import { ANCHOR_CORNER } from "@/components/file-thumbnail/types";

interface TiffWorkerRequest extends ThumbnailWorkerMessage {
  buffer: ArrayBuffer;
  targetWidth: number;
}

interface TiffWorkerReply extends ThumbnailWorkerMessage {
  id: number;
  ok: boolean;
  blob?: Blob;
  error?: string;
}

const tiffWorkerClient = createThumbnailWorkerClient<
  TiffWorkerRequest,
  TiffWorkerReply
>({
  createWorker: () =>
    new Worker(new URL("../../file-thumbnail-tiff.worker", import.meta.url)),
  resolve: (response) =>
    response.ok && response.blob ? response.blob : undefined,
  reject: (response) => response.error ?? "TIFF decode failed",
});

function decodeTiffInWorker(buffer: ArrayBuffer): Promise<Blob> {
  return tiffWorkerClient.request<Blob>({
    request: { buffer, targetWidth: TIFF_THUMBNAIL_TARGET_WIDTH },
    transfer: [buffer],
  });
}

const tiffCache = createThumbnailArtifactCache<Blob>({ maxEntries: 48 });

function getTiffFirstPageBlob(
  meta: ThumbnailFileMeta,
  content: ThumbnailBytesContent,
  thumbnailKey: string,
): Promise<Blob> {
  return cachedThumbnailResource(tiffCache, thumbnailKey, () =>
    withThumbnailDecodeSlot(() =>
      withThumbnailFormatError(
        "image",
        "decode_failed",
        meta.fileName,
        "Failed to decode TIFF thumbnail",
        () =>
          timedThumbnail(`tiff:total ${shortName(meta)}`, async () => {
            const buf = await timedThumbnail("tiff:fetch", () =>
              content.readBytes(),
            );
            return timedThumbnail("tiff:worker-decode", () =>
              decodeTiffInWorker(buf),
            );
          }),
      ),
    ),
  );
}

export function TiffFirstPage({
  resource,
  thumbnailKey,
  anchor,
  onError,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  const blob = useThumbnailResource(
    getTiffFirstPageBlob(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  return <TiffBlobImage blob={blob} anchor={anchor} onError={onError} />;
}

function TiffBlobImage({
  blob,
  anchor,
  onError,
}: {
  blob: Blob;
  anchor: ThumbnailAnchor;
  onError: (error: unknown) => void;
}) {
  const url = useObjectUrl(blob);

  if (!url) return <FileThumbnailShimmer />;

  return (
    <div className="absolute inset-0 overflow-hidden bg-white">
      <img
        src={url}
        alt=""
        className={cn("absolute block w-full", ANCHOR_CORNER[anchor])}
        onError={() => onError(createThumbnailImageLoadError())}
      />
    </div>
  );
}
components/file-thumbnail/renderers/use-object-url.ts
"use client";

import * as React from "react";
import { useKeyedMountEffect } from "@/hooks/use-keyed-mount-effect";
import { joinEffectKey } from "@/lib/effect-key";

export function useObjectUrl(blob: Blob | null): string | null {
  const [url, setUrl] = React.useState<string | null>(null);

  useKeyedMountEffect(joinEffectKey([blob]), () => {
    if (!blob) {
      setUrl(null);
      return;
    }

    const nextUrl = URL.createObjectURL(blob);
    setUrl(nextUrl);
    return () => URL.revokeObjectURL(nextUrl);
  });

  return url;
}
components/file-thumbnail/renderers/xlsx-thumbnail.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import { GridTable } from "@/components/file-thumbnail/renderers/layout";
import {
  cachedThumbnailResource,
  createThumbnailArtifactCache,
} from "@/components/file-thumbnail/thumbnail-cache";
import { withThumbnailDecodeSlot } from "@/components/file-thumbnail/thumbnail-decode-queue";
import { withThumbnailFormatError } from "@/components/file-thumbnail/thumbnail-errors";
import {
  XLSX_THUMBNAIL_MAX_COLUMNS,
  XLSX_THUMBNAIL_MAX_ROWS,
} from "@/components/file-thumbnail/thumbnail-limits";
import {
  shortName,
  timedThumbnail,
} from "@/components/file-thumbnail/thumbnail-profile";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  thumbnailFileMeta,
  type ThumbnailBytesContent,
  type ThumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";
import {
  createThumbnailWorkerClient,
  type ThumbnailWorkerMessage,
} from "@/components/file-thumbnail/thumbnail-worker-client";

interface XlsxPreview {
  rows: string[][];
}

interface XlsxWorkerRequest extends ThumbnailWorkerMessage {
  buffer: ArrayBuffer;
  maxRows: number;
  maxCols: number;
}

interface XlsxWorkerReply extends ThumbnailWorkerMessage {
  id: number;
  ok: boolean;
  rows?: string[][];
  error?: string;
}

const xlsxWorkerClient = createThumbnailWorkerClient<
  XlsxWorkerRequest,
  XlsxWorkerReply
>({
  createWorker: () =>
    new Worker(new URL("../../file-thumbnail-xlsx.worker", import.meta.url)),
  resolve: (response) =>
    response.ok && response.rows ? response.rows : undefined,
  reject: (response) => response.error ?? "XLSX parse failed",
});

function parseXlsxInWorker(buffer: ArrayBuffer): Promise<string[][]> {
  return xlsxWorkerClient.request<string[][]>({
    request: {
      buffer,
      maxRows: XLSX_THUMBNAIL_MAX_ROWS,
      maxCols: XLSX_THUMBNAIL_MAX_COLUMNS,
    },
    transfer: [buffer],
  });
}

const xlsxCache = createThumbnailArtifactCache<XlsxPreview>({
  maxEntries: 64,
});

function getXlsxPreview(
  meta: ThumbnailFileMeta,
  content: ThumbnailBytesContent,
  thumbnailKey: string,
): Promise<XlsxPreview> {
  return cachedThumbnailResource(xlsxCache, thumbnailKey, () =>
    withThumbnailDecodeSlot(() =>
      timedThumbnail(`xlsx:total ${shortName(meta)}`, async () => {
        const rows = await withThumbnailFormatError(
          "xlsx",
          "parse_failed",
          meta.fileName,
          "Failed to parse spreadsheet thumbnail",
          async () => {
            const buf = await content.readBytes();
            return timedThumbnail("xlsx:worker-parse", () =>
              parseXlsxInWorker(buf),
            );
          },
        );
        return { rows };
      }),
    ),
  );
}

export function XlsxFirstSheet({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const { rows } = useThumbnailResource(
    getXlsxPreview(thumbnailFileMeta(resource), resource.content, thumbnailKey),
  );
  return <GridTable rows={rows} />;
}
components/file-thumbnail-tiff.worker.ts
// Decodes the FIRST page of a TIFF and downscales it to a thumbnail-sized PNG,
// entirely off the main thread. UTIF's decode, the RGBA conversion, the resize,
// and the PNG encode are all synchronous CPU — running them here keeps a grid of
// TIFF thumbnails from janking the UI. We only ever touch ifds[0], so a 200-page
// scan costs the same as a one-page one.
import UTIF from "utif";

interface Ifd {
  t256?: number[];
  t257?: number[];
  width?: number;
  height?: number;
}
interface Utif {
  decode(buf: ArrayBuffer): Ifd[];
  decodeImage(buf: ArrayBuffer, ifd: Ifd): void;
  toRGBA8(ifd: Ifd): Uint8Array;
}
const utif = UTIF as Utif;

interface Req {
  id: number;
  buffer: ArrayBuffer;
  targetWidth: number;
}

const ctx = self as unknown as Worker;

ctx.onmessage = async (event: MessageEvent<Req>) => {
  const { id, buffer, targetWidth } = event.data;
  try {
    const ifds = utif.decode(buffer);
    if (!ifds.length) throw new Error("TIFF has no frames");
    const ifd = ifds[0];
    utif.decodeImage(buffer, ifd);
    const rgba = utif.toRGBA8(ifd);
    const width = ifd.t256?.[0] ?? ifd.width ?? 0;
    const height = ifd.t257?.[0] ?? ifd.height ?? 0;
    if (!width || !height) throw new Error("TIFF has no dimensions");

    const dw = Math.min(width, targetWidth);
    const dh = Math.max(1, Math.round((dw / width) * height));
    const pixels = new Uint8ClampedArray(rgba.length);
    pixels.set(rgba);
    const source = new ImageData(pixels, width, height);
    const bitmap = await createImageBitmap(source, {
      resizeWidth: dw,
      resizeHeight: dh,
      resizeQuality: "high",
    });
    const canvas = new OffscreenCanvas(dw, dh);
    const c = canvas.getContext("2d");
    if (!c) throw new Error("No 2d context");
    c.drawImage(bitmap, 0, 0);
    bitmap.close();
    const blob = await canvas.convertToBlob({ type: "image/png" });
    ctx.postMessage({ id, ok: true, blob });
  } catch (err) {
    ctx.postMessage({
      id,
      ok: false,
      error: err instanceof Error ? err.message : String(err),
    });
  }
};
components/file-thumbnail-xlsx.worker.ts
// Parses the first rows of a spreadsheet's first sheet off the main thread.
// XLSX.read is fully synchronous CPU and was the largest UI-thread block of the
// thumbnail grid; running it here keeps the page responsive. `sheetRows` stops
// the parser early so a 100k-row workbook costs the same as a 16-row one.
import * as XLSX from "@e965/xlsx";

import { isSpreadsheetContainer } from "@/lib/xlsx-worker-protocol";

interface Req {
  id: number;
  buffer: ArrayBuffer;
  maxRows: number;
  maxCols: number;
}

const ctx = self as unknown as Worker;

ctx.onmessage = (event: MessageEvent<Req>) => {
  const { id, buffer, maxRows, maxCols } = event.data;
  try {
    // SheetJS sniffs arbitrary bytes into a stub sheet; reject non-spreadsheet
    // input so a mislabeled file yields no thumbnail rather than junk cells.
    if (!isSpreadsheetContainer(buffer)) {
      throw new Error("File is not a recognized spreadsheet (.xlsx or .xls).");
    }
    const wb = XLSX.read(new Uint8Array(buffer), {
      type: "array",
      sheetRows: maxRows,
    });
    const ws = wb.Sheets[wb.SheetNames[0]];
    const grid = XLSX.utils.sheet_to_json<string[]>(ws, {
      header: 1,
      blankrows: false,
      defval: "",
      raw: false,
    });
    const rows = grid
      .slice(0, maxRows)
      .map((r) =>
        Array.isArray(r) ? r.slice(0, maxCols).map((c) => `${c ?? ""}`) : [],
      );
    ctx.postMessage({ id, ok: true, rows });
  } catch (err) {
    ctx.postMessage({
      id,
      ok: false,
      error: err instanceof Error ? err.message : String(err),
    });
  }
};
components/file-thumbnail/renderers/code-thumbnail.tsx
"use client";

import * as React from "react";

import type { ViewerResource } from "@/lib/viewer-resource";
import { formatCodeThumbnailText } from "@/components/file-thumbnail/thumbnail-code";
import { useThumbnailResource } from "@/components/file-thumbnail/thumbnail-resource";
import {
  getThumbnailText,
  thumbnailFileMeta,
} from "@/components/file-thumbnail/thumbnail-text";

const CODE_THUMBNAIL_MAX_LINES = 60;
const CODE_THUMBNAIL_FONT_SIZE = 5;
const CODE_THUMBNAIL_LINE_HEIGHT = 1.5;
const CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS = "w-2.5";

export function CodeThumbnail({
  resource,
  thumbnailKey,
}: {
  resource: ViewerResource;
  thumbnailKey: string;
}) {
  const raw = useThumbnailResource(
    getThumbnailText(
      thumbnailFileMeta(resource),
      resource.content,
      thumbnailKey,
    ),
  );
  const text = React.useMemo(
    () => formatCodeThumbnailText(raw, resource.fileName, resource.mimeType),
    [raw, resource.fileName, resource.mimeType],
  );
  const lines = React.useMemo(
    () =>
      text
        .replace(/\r?\n$/, "")
        .split(/\r\n?|\n/)
        .slice(0, CODE_THUMBNAIL_MAX_LINES),
    [text],
  );

  return (
    <div
      data-slot="code-thumbnail"
      className="bg-code text-code-foreground absolute inset-0 overflow-hidden"
    >
      <div
        aria-hidden
        className={`absolute inset-y-0 left-0 ${CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS} bg-code-highlight`}
      />
      <div
        className="relative font-mono"
        style={{
          fontSize: CODE_THUMBNAIL_FONT_SIZE,
          lineHeight: CODE_THUMBNAIL_LINE_HEIGHT,
        }}
      >
        {lines.map((line, i) => (
          <div key={i} className="flex">
            <span
              className={`${CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS} text-code-number shrink-0 pr-px text-right select-none`}
            >
              {i + 1}
            </span>
            <span className="pl-0.5 whitespace-pre">{line || " "}</span>
          </div>
        ))}
      </div>
    </div>
  );
}
components/file-thumbnail/thumbnail-code.ts
import type { ViewerDescriptor } from "@/lib/viewer-source";
import { extensionOf } from "@/lib/viewer-source";

const CODE_THUMBNAIL_EXTENSIONS = new Set([
  "bash",
  "c",
  "cc",
  "cjs",
  "cpp",
  "cs",
  "css",
  "env",
  "go",
  "graphql",
  "h",
  "ini",
  "java",
  "js",
  "json",
  "json5",
  "jsonl",
  "jsx",
  "kt",
  "less",
  "log",
  "lua",
  "mjs",
  "ndjson",
  "php",
  "pl",
  "proto",
  "py",
  "r",
  "rb",
  "rs",
  "scala",
  "scss",
  "sh",
  "sql",
  "svelte",
  "swift",
  "toml",
  "ts",
  "tsx",
  "vue",
  "xml",
  "yaml",
  "yml",
  "zsh",
]);

export function isCodeThumbnailDescriptor(
  descriptor: ViewerDescriptor,
): boolean {
  const extension = extensionOf(descriptor.displayName);
  if (extension && CODE_THUMBNAIL_EXTENSIONS.has(extension)) return true;

  const mime = descriptor.mimeType?.toLowerCase().split(";")[0].trim();
  return (
    mime === "application/json" ||
    mime === "application/xml" ||
    mime === "text/javascript" ||
    mime === "application/javascript" ||
    Boolean(mime?.endsWith("+json") || mime?.endsWith("+xml"))
  );
}

export function formatCodeThumbnailText(
  text: string,
  fileName: string,
  mimeType?: string,
) {
  if (!isStrictJson(fileName, mimeType)) return text;

  try {
    return JSON.stringify(JSON.parse(text), null, 2);
  } catch {
    return text;
  }
}

function isStrictJson(fileName: string, mimeType?: string) {
  const extension = extensionOf(fileName);
  if (extension === "json") return true;
  if (
    extension === "json5" ||
    extension === "jsonl" ||
    extension === "ndjson"
  ) {
    return false;
  }

  const mime = mimeType?.toLowerCase().split(";")[0].trim();
  return mime === "application/json" || Boolean(mime?.endsWith("+json"));
}
registry/new-york-v4/ui/utif.d.ts
// Ambient shim for the `utif` package, which ships no type declarations
// (and has no @types/utif on npm). shadcn's registry build strips leading
// comments from source files, so an inline `@ts-expect-error` above the import
// does not survive into the published item — a real declaration is required for
// a fresh consumer install to typecheck. The decode workers cast the default
// export to their own local shapes, so an opaque `any` default is sufficient.
declare module "utif" {
   
  const UTIF: any;
  export default UTIF;
}
registry/new-york-v4/ui/docx-viewer-core.ts
import type * as DocxPreview from "docx-preview";

import {
  isViewerFormatError,
  ViewerFormatError,
  type ViewerFormatErrorMapperOptions,
} from "@/lib/viewer-errors";

export const DOCX_MIN_SCALE = 0.1;
export const DOCX_MAX_SCALE = 5;
export const DOCX_ZOOM_STEP = 1.2;

export const DEFAULT_DOCX_PAGE_WIDTH = 816;
export const DEFAULT_DOCX_PAGE_HEIGHT = 1056;

export const DOCX_RENDER_OPTIONS: Partial<DocxPreview.Options> = {
  inWrapper: true,
  breakPages: true,
  ignoreLastRenderedPageBreak: false,
  experimental: true,
  renderHeaders: true,
  renderFooters: true,
  renderFootnotes: true,
};

export const DOCX_SCOPED_STYLES = `
[data-slot="docx-viewer"] .docx-wrapper {
  background: transparent;
  padding: 0;
  gap: 1rem;
}
[data-slot="docx-viewer"] .docx-wrapper > section.docx {
  margin-bottom: 0;
  box-shadow: 0 0 0 1px var(--border), 0 1px 2px 0 rgb(0 0 0 / 0.05);
}`;

export function clampDocxScale(value: number) {
  return clamp(value, DOCX_MIN_SCALE, DOCX_MAX_SCALE);
}

export function normalizeDocxScale(value: number | null | undefined) {
  if (value == null) return null;
  if (Number.isNaN(value)) return 1;
  return clampDocxScale(value);
}

export function clamp(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value));
}

export function positivePixel(value: number) {
  return Number.isFinite(value) && value > 0 ? value : null;
}

export function toDocxFormatError(
  error: unknown,
  options: ViewerFormatErrorMapperOptions,
): ViewerFormatError {
  if (isViewerFormatError(error)) return error;
  return new ViewerFormatError({
    format: "docx",
    kind: options.kind,
    message: options.message,
    cause: error,
  });
}
registry/new-york-v4/ui/docx-viewer-layout.ts
import { clamp } from "./docx-viewer-core";

export const DOCX_PAGE_GAP_PX = 16;
export const DOCX_VIEWER_PADDING_PX = 16;
export const DOCX_READING_MARKER_RATIO = 0.2;
export const DOCX_VIRTUAL_OVERSCAN_PX = 1000;
export const DOCX_FALLBACK_VIEWPORT_HEIGHT_PX = 768;

export interface DocxPageMetric {
  pageNumber: number;
  width: number;
  height: number;
  top: number;
  bottom: number;
}

export interface DocxPageLayout {
  pages: readonly DocxPageMetric[];
  totalHeight: number;
}

export interface DocxPageWindow {
  afterHeight: number;
  beforeHeight: number;
  endIndex: number;
  renderedBottom: number;
  renderedHeight: number;
  renderedTop: number;
  startIndex: number;
  stickyOffset: number;
}

export type DocxReadingAnchor =
  | {
      kind: "top";
    }
  | {
      kind: "page";
      pageNumber: number;
      yPercent: number;
    };

export function createDocxPageLayout(
  sizes: readonly (readonly [number, number])[],
  gap = DOCX_PAGE_GAP_PX,
): DocxPageLayout {
  let top = 0;
  const pages = sizes.map(([width, height], index) => {
    const page: DocxPageMetric = {
      pageNumber: index + 1,
      width,
      height,
      top,
      bottom: top + height,
    };
    top = page.bottom + gap;
    return page;
  });

  return {
    pages,
    totalHeight: pages.length ? pages[pages.length - 1]!.bottom : 0,
  };
}

export function findDocxPageByMarker({
  layout,
  scale,
  scrollTop,
  viewportHeight,
}: {
  layout: DocxPageLayout | null;
  scale: number;
  scrollTop: number;
  viewportHeight: number;
}) {
  const pages = layout?.pages;
  if (!pages?.length) return null;

  const marker = scrollTop + viewportHeight * DOCX_READING_MARKER_RATIO;
  const y = Math.max(0, (marker - DOCX_VIEWER_PADDING_PX) / safeScale(scale));
  let low = 0;
  let high = pages.length - 1;
  let current = pages[0]!;

  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    const page = pages[mid]!;
    if (page.top <= y) {
      current = page;
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return current;
}

export function captureDocxReadingAnchorFromLayout({
  layout,
  scale,
  scrollTop,
  viewportHeight,
}: {
  layout: DocxPageLayout | null;
  scale: number;
  scrollTop: number;
  viewportHeight: number;
}): DocxReadingAnchor {
  if (scrollTop <= 0) return { kind: "top" };

  const page = findDocxPageByMarker({
    layout,
    scale,
    scrollTop,
    viewportHeight,
  });
  if (!page || page.height <= 0) return { kind: "top" };

  const marker = scrollTop + viewportHeight * DOCX_READING_MARKER_RATIO;
  const y = Math.max(0, (marker - DOCX_VIEWER_PADDING_PX) / safeScale(scale));

  return {
    kind: "page",
    pageNumber: page.pageNumber,
    yPercent: clamp((y - page.top) / page.height, 0, 1),
  };
}

export function restoreDocxReadingAnchorFromLayout({
  anchor,
  layout,
  maxScrollTop,
  scale,
  viewportHeight,
}: {
  anchor: DocxReadingAnchor;
  layout: DocxPageLayout | null;
  maxScrollTop: number;
  scale: number;
  viewportHeight: number;
}) {
  if (anchor.kind === "top") return 0;

  const page = layout?.pages[anchor.pageNumber - 1];
  if (!page || page.height <= 0) return null;

  const y = (page.top + page.height * anchor.yPercent) * safeScale(scale);
  const marker = viewportHeight * DOCX_READING_MARKER_RATIO;
  return clamp(DOCX_VIEWER_PADDING_PX + y - marker, 0, maxScrollTop);
}

export function createDocxPageWindowFromScroll({
  layout,
  overscanPx = DOCX_VIRTUAL_OVERSCAN_PX,
  scale,
  scrollTop,
  viewportHeight,
}: {
  layout: DocxPageLayout | null;
  overscanPx?: number;
  scale: number;
  scrollTop: number;
  viewportHeight: number;
}): DocxPageWindow {
  const pages = layout?.pages;
  if (!layout || !pages?.length) {
    return emptyDocxPageWindow();
  }

  const z = safeScale(scale);
  const viewHeight = safeViewportHeight(viewportHeight) / z;
  const overscan = Math.max(0, overscanPx) / z;
  const viewTop = Math.max(0, (scrollTop - DOCX_VIEWER_PADDING_PX) / z);
  const windowTop = Math.max(0, viewTop - overscan);
  const windowBottom = Math.min(
    layout.totalHeight,
    viewTop + viewHeight + overscan,
  );

  let startIndex = pages.findIndex((page) => page.bottom >= windowTop);
  if (startIndex === -1) startIndex = pages.length - 1;

  let endIndex = startIndex;
  while (
    endIndex < pages.length &&
    pages[endIndex]!.top <= Math.max(windowBottom, pages[startIndex]!.top)
  ) {
    endIndex += 1;
  }
  if (endIndex === startIndex)
    endIndex = Math.min(pages.length, startIndex + 1);

  return createDocxPageWindowFromRange({
    endIndex,
    layout,
    startIndex,
    viewportHeight: viewHeight,
  });
}

export function createDocxPageWindowForPage({
  layout,
  pageIndex,
  scale,
  viewportHeight,
}: {
  layout: DocxPageLayout | null;
  pageIndex: number;
  scale: number;
  viewportHeight: number;
}): DocxPageWindow {
  const pages = layout?.pages;
  if (!layout || !pages?.length) return emptyDocxPageWindow();
  const safePageIndex = clamp(Math.floor(pageIndex), 0, pages.length - 1);
  return createDocxPageWindowFromRange({
    endIndex: safePageIndex + 1,
    layout,
    startIndex: safePageIndex,
    viewportHeight: safeViewportHeight(viewportHeight) / safeScale(scale),
  });
}

function createDocxPageWindowFromRange({
  endIndex,
  layout,
  startIndex,
  viewportHeight,
}: {
  endIndex: number;
  layout: DocxPageLayout;
  startIndex: number;
  viewportHeight: number;
}): DocxPageWindow {
  const pages = layout.pages;
  const start = clamp(Math.floor(startIndex), 0, pages.length);
  const end = clamp(Math.ceil(endIndex), start, pages.length);
  const first = pages[start];
  const last = pages[end - 1];
  if (!first || !last) return emptyDocxPageWindow();

  const renderedTop = first.top;
  const renderedBottom = last.bottom;
  const renderedHeight = renderedBottom - renderedTop;

  return {
    afterHeight: Math.max(0, layout.totalHeight - renderedBottom),
    beforeHeight: renderedTop,
    endIndex: end,
    renderedBottom,
    renderedHeight,
    renderedTop,
    startIndex: start,
    stickyOffset: -Math.max(0, renderedHeight - Math.max(1, viewportHeight)),
  };
}

function emptyDocxPageWindow(): DocxPageWindow {
  return {
    afterHeight: 0,
    beforeHeight: 0,
    endIndex: 0,
    renderedBottom: 0,
    renderedHeight: 0,
    renderedTop: 0,
    startIndex: 0,
    stickyOffset: 0,
  };
}

function safeViewportHeight(value: number) {
  return Number.isFinite(value) && value > 0
    ? value
    : DOCX_FALLBACK_VIEWPORT_HEIGHT_PX;
}

function safeScale(scale: number) {
  return Number.isFinite(scale) && scale > 0 ? scale : 1;
}
registry/new-york-v4/ui/docx-viewer-render-cache.ts
import { lruGet, lruSet } from "./viewer-lru-cache";

export const DOCX_RENDER_CACHE_MAX_ENTRIES = 4;

export type DocxPageSize = readonly [number, number];

export interface DocxRenderCacheEntry {
  pageSizes: readonly DocxPageSize[];
  renderHost: HTMLElement;
}

export interface DocxRenderCacheHit {
  pageSizes: readonly DocxPageSize[];
  renderHost: HTMLElement;
}

const UNSAFE_DOCX_RENDER_CACHE_SELECTOR =
  "audio, canvas, embed, iframe, object, video";

const docxRenderCache = new Map<string, DocxRenderCacheEntry>();
const pendingDocxRenderCache = new Map<
  string,
  Promise<DocxRenderCacheEntry | null>
>();

export function readDocxRenderCache(key: string): DocxRenderCacheHit | null {
  const entry = lruGet(docxRenderCache, key);
  return entry ? cloneDocxRenderCacheEntry(entry) : null;
}

export function readPendingDocxRenderCache(
  key: string,
): Promise<DocxRenderCacheEntry | null> | null {
  return pendingDocxRenderCache.get(key) ?? null;
}

export function writePendingDocxRenderCache(
  key: string,
  promise: Promise<DocxRenderCacheEntry | null>,
) {
  pendingDocxRenderCache.set(key, promise);
  void promise
    .finally(() => {
      if (pendingDocxRenderCache.get(key) === promise) {
        pendingDocxRenderCache.delete(key);
      }
    })
    .catch(() => undefined);
}

export function writeDocxRenderCache({
  key,
  pageSizes,
  renderHost,
}: {
  key: string;
  pageSizes: readonly DocxPageSize[];
  renderHost: HTMLElement;
}): DocxRenderCacheEntry | null {
  if (
    pageSizes.length === 0 ||
    !renderHost.querySelector(".docx-wrapper > section.docx")
  ) {
    return null;
  }
  if (!canCacheDocxRenderHost(renderHost)) return null;

  const cachedRenderHost = renderHost.cloneNode(true);
  if (!(cachedRenderHost instanceof HTMLElement)) return null;

  const entry: DocxRenderCacheEntry = {
    pageSizes,
    renderHost: cachedRenderHost,
  };
  lruSet(docxRenderCache, key, entry, undefined, DOCX_RENDER_CACHE_MAX_ENTRIES);
  return entry;
}

export function cloneDocxRenderCacheEntry(
  entry: DocxRenderCacheEntry,
): DocxRenderCacheHit {
  return {
    pageSizes: entry.pageSizes,
    renderHost: entry.renderHost.cloneNode(true) as HTMLElement,
  };
}

export function resetDocxRenderCacheForTests() {
  docxRenderCache.clear();
  pendingDocxRenderCache.clear();
}

function canCacheDocxRenderHost(renderHost: HTMLElement) {
  return !renderHost.querySelector(UNSAFE_DOCX_RENDER_CACHE_SELECTOR);
}
registry/new-york-v4/ui/docx-viewer-render.ts
import type * as DocxPreview from "docx-preview";

import { ViewerFormatError } from "@/lib/viewer-errors";

import {
  DEFAULT_DOCX_PAGE_HEIGHT,
  DEFAULT_DOCX_PAGE_WIDTH,
  DOCX_RENDER_OPTIONS,
  positivePixel,
} from "./docx-viewer-core";
import {
  createDocxPageLayout,
  type DocxPageLayout,
  type DocxPageWindow,
} from "./docx-viewer-layout";
import {
  readDocxRenderCache,
  readPendingDocxRenderCache,
  writeDocxRenderCache,
  writePendingDocxRenderCache,
  type DocxPageSize,
  type DocxRenderCacheHit,
} from "./docx-viewer-render-cache";

let docxPromise: Promise<typeof DocxPreview> | null = null;

export interface DocxRenderedDocument {
  after: HTMLDivElement;
  before: HTMLDivElement;
  mountedEnd: number;
  mountedStart: number;
  pageLayout: DocxPageLayout;
  pages: readonly HTMLElement[];
  sticky: HTMLElement;
  wrapper: HTMLElement;
}

type DocxRenderBuffer = ArrayBuffer | (() => ArrayBuffer);

export function loadDocxPreview() {
  if (!docxPromise) {
    docxPromise = import("docx-preview").catch((error) => {
      docxPromise = null;
      throw error;
    });
  }
  return docxPromise;
}

export async function renderDocxPreview(
  buffer: ArrayBuffer,
  docxPreviewPromise = loadDocxPreview(),
) {
  const { renderAsync } = await docxPreviewPromise;
  const renderHost = document.createElement("div");
  await renderAsync(buffer, renderHost, undefined, DOCX_RENDER_OPTIONS);
  return renderHost;
}

export async function renderCachedDocxPreview({
  buffer,
  cacheKey,
  docxPreviewPromise,
  getScale,
}: {
  buffer: DocxRenderBuffer;
  cacheKey: string;
  docxPreviewPromise?: Promise<typeof DocxPreview>;
  getScale: () => number;
}): Promise<DocxRenderCacheHit> {
  const cached = readDocxRenderCache(cacheKey);
  if (cached) return cached;

  const pending = readPendingDocxRenderCache(cacheKey);
  if (pending) {
    const pendingEntry = await pending;
    if (pendingEntry) {
      return {
        pageSizes: pendingEntry.pageSizes,
        renderHost: pendingEntry.renderHost.cloneNode(true) as HTMLElement,
      };
    }
  }

  const renderPromise = renderDocxPreview(
    resolveDocxRenderBuffer(buffer),
    docxPreviewPromise ?? loadDocxPreview(),
  ).then((renderHost) => {
    const pageSizes = collectDocxPageSizes(renderHost, getScale());
    const cacheEntry = writeDocxRenderCache({
      key: cacheKey,
      pageSizes,
      renderHost,
    });

    return { cacheEntry, pageSizes, renderHost };
  });
  writePendingDocxRenderCache(
    cacheKey,
    renderPromise.then((result) => result.cacheEntry),
  );

  const result = await renderPromise;
  return {
    pageSizes: result.pageSizes,
    renderHost: result.renderHost,
  };
}

function resolveDocxRenderBuffer(buffer: DocxRenderBuffer) {
  return typeof buffer === "function" ? buffer() : buffer;
}

export function commitDocxRender({
  host,
  pageSizes,
  renderHost,
  scale,
}: {
  host: HTMLElement;
  pageSizes?: readonly DocxPageSize[];
  renderHost: HTMLElement;
  scale: number;
}) {
  const wrapper = renderHost.querySelector<HTMLElement>(".docx-wrapper");
  if (!wrapper) {
    throw new ViewerFormatError({
      format: "docx",
      kind: "render_failed",
      message: "DOCX render produced no pages.",
    });
  }
  const pages = Array.from(
    renderHost.querySelectorAll<HTMLElement>(".docx-wrapper > section.docx"),
  );
  if (!pages.length) {
    throw new ViewerFormatError({
      format: "docx",
      kind: "render_failed",
      message: "DOCX render produced no pages.",
    });
  }
  const z = scale || 1;
  const sizes =
    pageSizes && pageSizes.length === pages.length
      ? pageSizes
      : pages.map((el) => pageSize(el, z));
  pages.forEach((el, i) => {
    el.dataset.pageNumber = String(i + 1);
    el.style.contentVisibility = "auto";
    const [width, height] = sizes[i]!;
    el.style.containIntrinsicSize = `${width}px ${height}px`;
  });
  const pageLayout = createDocxPageLayout(sizes);
  const before = document.createElement("div");
  const after = document.createElement("div");
  before.dataset.slot = "docx-sticky-before-buffer";
  wrapper.dataset.slot = "docx-sticky-window";
  after.dataset.slot = "docx-sticky-after-buffer";
  before.setAttribute("aria-hidden", "true");
  after.setAttribute("aria-hidden", "true");
  wrapper.style.position = "sticky";
  wrapper.style.left = "0";
  wrapper.style.overflow = "visible";
  wrapper.style.width = "100%";

  const staticNodes = Array.from(renderHost.childNodes).filter(
    (node) => node !== wrapper,
  );
  wrapper.replaceChildren();
  host.replaceChildren(...staticNodes, before, wrapper, after);

  const virtualDocument: DocxRenderedDocument = {
    after,
    before,
    mountedEnd: 0,
    mountedStart: 0,
    pageLayout,
    pages,
    sticky: wrapper,
    wrapper,
  };
  return {
    numPages: pages.length,
    pageWidth: pages.length ? sizes[0][0] : null,
    pageLayout,
    virtualDocument,
  };
}

export function projectDocxPages(
  document: DocxRenderedDocument,
  window: DocxPageWindow,
) {
  document.before.style.height = `${window.beforeHeight}px`;
  document.after.style.height = `${window.afterHeight}px`;
  document.sticky.style.top = `${window.stickyOffset}px`;
  document.sticky.style.bottom = `${window.stickyOffset}px`;
  document.sticky.style.height = `${window.renderedHeight}px`;

  const start = window.startIndex;
  const end = window.endIndex;
  if (start === document.mountedStart && end === document.mountedEnd) return;

  if (
    document.mountedEnd <= document.mountedStart ||
    end <= start ||
    end <= document.mountedStart ||
    start >= document.mountedEnd
  ) {
    document.wrapper.replaceChildren(...document.pages.slice(start, end));
    document.mountedStart = start;
    document.mountedEnd = end;
    return;
  }

  for (
    let index = document.mountedStart;
    index < Math.min(start, document.mountedEnd);
    index += 1
  ) {
    document.pages[index]?.remove();
  }
  for (
    let index = Math.max(end, document.mountedStart);
    index < document.mountedEnd;
    index += 1
  ) {
    document.pages[index]?.remove();
  }

  if (start < document.mountedStart) {
    const fragment = globalThis.document.createDocumentFragment();
    for (let index = start; index < document.mountedStart; index += 1) {
      const page = document.pages[index];
      if (page) fragment.append(page);
    }
    document.wrapper.insertBefore(fragment, document.wrapper.firstChild);
  }

  if (end > document.mountedEnd) {
    const fragment = globalThis.document.createDocumentFragment();
    for (
      let index = Math.max(document.mountedEnd, start);
      index < end;
      index += 1
    ) {
      const page = document.pages[index];
      if (page) fragment.append(page);
    }
    document.wrapper.append(fragment);
  }

  document.mountedStart = start;
  document.mountedEnd = end;
}

function collectDocxPageSizes(
  renderHost: HTMLElement,
  scale: number,
): readonly DocxPageSize[] {
  const pages = Array.from(
    renderHost.querySelectorAll<HTMLElement>(".docx-wrapper > section.docx"),
  );
  const z = scale || 1;
  return pages.map((el) => pageSize(el, z));
}

function pageSize(el: HTMLElement, scale: number) {
  const styledWidth = positivePixel(Math.round(cssLengthToPx(el.style.width)));
  const styledHeight = positivePixel(
    Math.round(
      cssLengthToPx(el.style.height) || cssLengthToPx(el.style.minHeight),
    ),
  );
  if (styledWidth && styledHeight) {
    return [styledWidth, styledHeight] as const;
  }

  const r = el.getBoundingClientRect();
  const width = positivePixel(Math.round(r.width / scale));
  const height = positivePixel(Math.round(r.height / scale));
  return [
    styledWidth ?? width ?? DEFAULT_DOCX_PAGE_WIDTH,
    styledHeight ?? height ?? DEFAULT_DOCX_PAGE_HEIGHT,
  ] as const;
}

function cssLengthToPx(value: string) {
  const match = value.trim().match(/^(-?\d*\.?\d+)(px|pt|in|cm|mm|pc)?$/i);
  if (!match) return 0;

  const amount = Number(match[1]);
  if (!Number.isFinite(amount) || amount <= 0) return 0;

  const unit = (match[2] ?? "px").toLowerCase();
  if (unit === "px") return amount;
  if (unit === "pt") return (amount * 96) / 72;
  if (unit === "in") return amount * 96;
  if (unit === "cm") return (amount * 96) / 2.54;
  if (unit === "mm") return (amount * 96) / 25.4;
  if (unit === "pc") return amount * 16;
  return 0;
}
registry/new-york-v4/ui/pdf-viewer-canvas.ts
export function getPdfCanvasPixelSize(
  cssSize: number,
  devicePixelRatio: number,
) {
  if (
    !Number.isFinite(cssSize) ||
    !Number.isFinite(devicePixelRatio) ||
    cssSize <= 0 ||
    devicePixelRatio <= 0
  ) {
    return 1;
  }
  return Math.max(1, Math.floor(cssSize * devicePixelRatio));
}
registry/new-york-v4/ui/pdf-viewer-render-cache.ts
import { useKeyedMountEffect } from "@/hooks/use-keyed-mount-effect";
import { joinEffectKey } from "@/lib/effect-key";

export const PDF_RENDERED_PAGE_CACHE_MAX_ENTRIES = 16;
export const PDF_RENDERED_PAGE_CACHE_MAX_PIXELS = 24_000_000;
// Only close lower-resolution renders of the same page may be stretched as
// resize previews; thumbnail-resolution bitmaps below this ratio would blur.
export const PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO = 0.5;

export type PdfRenderedPageSignature = {
  documentKey: string;
  pageNumber: number;
  scale: number;
  rotation: number;
  devicePixelRatio: number;
  viewportWidth: number;
  viewportHeight: number;
};

export type PdfRenderedPageCache = {
  clock: number;
  entries: Map<string, PdfRenderedPageCacheEntry>;
  resetKey: unknown;
};

export type PdfRenderedPageCacheEntry = PdfRenderedPageSignature & {
  canvas: HTMLCanvasElement;
  lastUsed: number;
  pixels: number;
};

type PdfRenderedPageCacheRecord = {
  cache: PdfRenderedPageCache;
  retainCount: number;
  resetKey: unknown;
};

const objectCacheRecords = new WeakMap<object, PdfRenderedPageCacheRecord>();
const primitiveCacheRecords = new Map<unknown, PdfRenderedPageCacheRecord>();

export function createPdfRenderedPageCache(
  resetKey: unknown,
): PdfRenderedPageCache {
  return {
    clock: 0,
    entries: new Map(),
    resetKey,
  };
}

export function usePdfRenderedPageCache(resetKey: unknown) {
  const record = getPdfRenderedPageCacheRecord(resetKey);

  useKeyedMountEffect(joinEffectKey([record]), () => {
    record.retainCount += 1;
    return () => releasePdfRenderedPageCacheRecord(record);
  });

  return record.cache;
}

export function readPdfRenderedPageCache(
  cache: PdfRenderedPageCache | undefined,
  requested: PdfRenderedPageSignature,
) {
  if (!cache) return null;

  let bestEntry: PdfRenderedPageCacheEntry | null = null;
  for (const entry of cache.entries.values()) {
    if (!doesRenderedPageSatisfyRequest(entry, requested)) continue;
    if (
      !bestEntry ||
      entry.pixels < bestEntry.pixels ||
      (entry.pixels === bestEntry.pixels && entry.lastUsed > bestEntry.lastUsed)
    ) {
      bestEntry = entry;
    }
  }

  if (!bestEntry) return null;
  cache.clock += 1;
  bestEntry.lastUsed = cache.clock;
  return bestEntry;
}

export function readPdfRenderedPageCachePreview(
  cache: PdfRenderedPageCache | undefined,
  requested: PdfRenderedPageSignature,
) {
  if (!cache) return null;

  let bestEntry: PdfRenderedPageCacheEntry | null = null;
  for (const entry of cache.entries.values()) {
    if (!doesRenderedPageMatchPreview(entry, requested)) continue;
    if (!bestEntry || entry.pixels > bestEntry.pixels) {
      bestEntry = entry;
    }
  }

  if (!bestEntry) return null;
  cache.clock += 1;
  bestEntry.lastUsed = cache.clock;
  return bestEntry;
}

export function writePdfRenderedPageCache({
  cache,
  rendered,
  sourceCanvas,
}: {
  cache: PdfRenderedPageCache | undefined;
  rendered: PdfRenderedPageSignature;
  sourceCanvas: HTMLCanvasElement;
}) {
  if (!cache || sourceCanvas.width <= 0 || sourceCanvas.height <= 0) return;

  const canvas = document.createElement("canvas");
  canvas.width = sourceCanvas.width;
  canvas.height = sourceCanvas.height;
  const context = canvas.getContext("2d");
  if (!context || typeof context.drawImage !== "function") return;

  context.drawImage(sourceCanvas, 0, 0);
  cache.clock += 1;
  const key = getPdfRenderedPageCacheKey(rendered);
  cache.entries.set(key, {
    ...rendered,
    canvas,
    lastUsed: cache.clock,
    pixels: canvas.width * canvas.height,
  });
  evictPdfRenderedPageCache(cache, key);
}

export function clearPdfRenderedPageCache(cache: PdfRenderedPageCache) {
  cache.clock = 0;
  cache.entries.clear();
}

function evictPdfRenderedPageCache(
  cache: PdfRenderedPageCache,
  retainedKey: string,
) {
  let totalPixels = getTotalCachedPixels(cache);

  while (
    cache.entries.size > PDF_RENDERED_PAGE_CACHE_MAX_ENTRIES ||
    totalPixels > PDF_RENDERED_PAGE_CACHE_MAX_PIXELS
  ) {
    const evicted = findPdfRenderedPageCacheEviction(cache, retainedKey);
    if (!evicted) return;

    totalPixels -= evicted.entry.pixels;
    cache.entries.delete(evicted.key);
  }
}

function findPdfRenderedPageCacheEviction(
  cache: PdfRenderedPageCache,
  retainedKey: string,
) {
  let oldest: { key: string; entry: PdfRenderedPageCacheEntry } | null = null;

  for (const [key, entry] of cache.entries) {
    if (key === retainedKey) continue;
    if (!oldest || entry.lastUsed < oldest.entry.lastUsed) {
      oldest = { key, entry };
    }
  }

  return oldest;
}

function getTotalCachedPixels(cache: PdfRenderedPageCache) {
  let pixels = 0;
  for (const entry of cache.entries.values()) {
    pixels += entry.pixels;
  }
  return pixels;
}

function doesRenderedPageSatisfyRequest(
  rendered: PdfRenderedPageSignature,
  requested: PdfRenderedPageSignature,
) {
  return (
    rendered.documentKey === requested.documentKey &&
    rendered.pageNumber === requested.pageNumber &&
    rendered.rotation === requested.rotation &&
    rendered.scale >= requested.scale &&
    rendered.viewportWidth >= requested.viewportWidth &&
    rendered.viewportHeight >= requested.viewportHeight &&
    hasMatchingPageAspectRatio(rendered, requested) &&
    rendered.devicePixelRatio >= requested.devicePixelRatio
  );
}

function doesRenderedPageMatchPreview(
  rendered: PdfRenderedPageSignature,
  requested: PdfRenderedPageSignature,
) {
  return (
    rendered.documentKey === requested.documentKey &&
    rendered.pageNumber === requested.pageNumber &&
    rendered.rotation === requested.rotation &&
    rendered.viewportWidth >=
      requested.viewportWidth * PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO &&
    rendered.viewportHeight >=
      requested.viewportHeight * PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO &&
    hasMatchingPageAspectRatio(rendered, requested)
  );
}

function getPdfRenderedPageCacheKey(rendered: PdfRenderedPageSignature) {
  return [
    rendered.documentKey,
    rendered.pageNumber,
    rendered.scale,
    rendered.rotation,
    rendered.devicePixelRatio,
    rendered.viewportWidth,
    rendered.viewportHeight,
  ].join(":");
}

function getPdfRenderedPageCacheRecord(
  resetKey: unknown,
): PdfRenderedPageCacheRecord {
  const records = isObjectCacheKey(resetKey)
    ? objectCacheRecords
    : primitiveCacheRecords;
  const cached = records.get(resetKey as never);
  if (cached) return cached;

  const record: PdfRenderedPageCacheRecord = {
    cache: createPdfRenderedPageCache(resetKey),
    retainCount: 0,
    resetKey,
  };
  records.set(resetKey as never, record);
  return record;
}

function releasePdfRenderedPageCacheRecord(record: PdfRenderedPageCacheRecord) {
  record.retainCount = Math.max(0, record.retainCount - 1);
  if (record.retainCount > 0) return;

  clearPdfRenderedPageCache(record.cache);
  if (isObjectCacheKey(record.resetKey)) {
    objectCacheRecords.delete(record.resetKey);
  } else {
    primitiveCacheRecords.delete(record.resetKey);
  }
}

function isObjectCacheKey(value: unknown): value is object {
  return (
    (typeof value === "object" && value !== null) || typeof value === "function"
  );
}

function hasMatchingPageAspectRatio(
  rendered: PdfRenderedPageSignature,
  requested: PdfRenderedPageSignature,
) {
  const renderedRatio = rendered.viewportWidth / rendered.viewportHeight;
  const requestedRatio = requested.viewportWidth / requested.viewportHeight;
  return Math.abs(renderedRatio - requestedRatio) < 0.0001;
}

More from retab

Browse all components in this library →