Files
orca/config/build-plugins/pdfjs-viewer-assets.ts
OrcaWin 54a19c2ba3 fix(pdf): render CJK text with pdf.js resources
Merged after PR-specific checks passed. CI failures are unrelated baseline findings in ClientHostedBrowserPagePane.markup.test.tsx and pane-title-update-global-scan-budget.test.tsx.
2026-09-19 01:28:09 -07:00

101 lines
3.1 KiB
TypeScript

import { createReadStream, cpSync, existsSync, mkdirSync, statSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, extname, isAbsolute, join, relative, resolve } from 'node:path'
import type { Plugin } from 'vite'
export const PDFJS_VIEWER_ASSET_DIRS = ['cmaps', 'standard_fonts', 'wasm'] as const
function isAssetDirectory(
value: string | undefined
): value is (typeof PDFJS_VIEWER_ASSET_DIRS)[number] {
return value !== undefined && PDFJS_VIEWER_ASSET_DIRS.some((directory) => directory === value)
}
function pdfjsRoot(): string {
return dirname(createRequire(import.meta.url).resolve('pdfjs-dist/package.json'))
}
function assetPath(root: string, pathname: string): string | undefined {
let decoded: string
try {
decoded = decodeURIComponent(pathname)
} catch {
return undefined
}
if (decoded.includes('\0') || decoded.includes('\\')) {
return undefined
}
const parts = decoded.split('/').filter(Boolean)
const directory = parts[0]
if (parts.length < 2 || !isAssetDirectory(directory)) {
return undefined
}
if (parts.some((part) => part === '.' || part === '..' || part.includes('\\'))) {
return undefined
}
const candidate = resolve(root, ...parts)
const base = resolve(root, directory)
const withinBase = relative(base, candidate)
if (withinBase.length === 0 || withinBase.startsWith('..') || isAbsolute(withinBase)) {
return undefined
}
return candidate
}
function copyAssets(root: string, outputDir: string): void {
for (const directory of PDFJS_VIEWER_ASSET_DIRS) {
const source = join(root, directory)
if (!existsSync(source)) {
throw new Error(`[pdfjs-viewer-assets] missing ${source}`)
}
cpSync(source, join(outputDir, directory), { recursive: true })
}
}
export function createPdfjsViewerAssetsPlugin(root = pdfjsRoot()): Plugin {
return {
name: 'pdfjs-viewer-assets',
configureServer(server) {
server.middlewares.use((request, response, next) => {
let pathname: string
try {
pathname = new URL(request.url ?? '/', 'http://localhost').pathname
} catch {
next()
return
}
const filePath = assetPath(root, pathname)
if (!filePath || (request.method !== 'GET' && request.method !== 'HEAD')) {
next()
return
}
let size: number
try {
size = statSync(filePath).size
} catch {
next()
return
}
response.statusCode = 200
response.setHeader('Content-Length', size)
response.setHeader(
'Content-Type',
extname(filePath) === '.wasm' ? 'application/wasm' : 'application/octet-stream'
)
if (request.method === 'HEAD') {
response.end()
return
}
createReadStream(filePath).pipe(response)
})
},
writeBundle(options) {
if (!options.dir) {
throw new Error('[pdfjs-viewer-assets] output directory is required')
}
mkdirSync(options.dir, { recursive: true })
copyAssets(root, options.dir)
}
}
}