Files
Jyotisha/frontend/src/lib/client-report-export.ts
T

73 lines
2.5 KiB
TypeScript

/**
* Client-side print export for the personal report.
*
* Deliberately print-only: `await document.fonts.ready` then `window.print()`.
* It never requests a server-side PDF endpoint, never launches a headless
* browser, and never rasterizes pixels: the user's own browser and OS render
* the PDF.
*/
export interface PrintPersonalReportOptions {
/** Title used as the browser's default PDF filename (document.title). */
title?: string;
}
/** Sanitize a report id into a safe PDF filename fragment (no path/query chars). */
export function safeReportFilename(reportId: string): string {
const cleaned = String(reportId ?? "")
.replace(/[^a-zA-Z0-9_-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 64);
return cleaned.length > 0 ? `jyotisha-report-${cleaned}` : "jyotisha-report";
}
export function isPrintSupported(): boolean {
return typeof window !== "undefined" && typeof window.print === "function";
}
/**
* Detect environments whose built-in browser cannot print reliably.
* WeChat's in-app browser is the known case; it should ask users to open the
* page in the system browser instead of producing a broken printout.
*/
export function detectPrintRestriction(userAgent?: string): {
restricted: boolean;
message?: string;
} {
const ua = typeof userAgent === "string" ? userAgent : typeof navigator !== "undefined" ? navigator.userAgent : "";
if (/MicroMessenger/i.test(ua)) {
return {
restricted: true,
message: "微信内置浏览器可能无法完整打印,请在系统浏览器中打开本页后打印。",
};
}
return { restricted: false };
}
/**
* Wait for web fonts, then open the browser print dialog.
* The page/PDF layout is computed on the user's device; nothing is uploaded.
*/
export async function printPersonalReport(options: PrintPersonalReportOptions = {}): Promise<void> {
if (typeof window === "undefined" || typeof window.print !== "function") {
return;
}
const fonts = typeof document !== "undefined" ? document.fonts : undefined;
if (fonts && typeof fonts.ready?.then === "function") {
await fonts.ready;
}
const previousTitle = typeof document !== "undefined" ? document.title : "";
const requested = typeof options.title === "string" ? options.title.trim() : "";
if (typeof document !== "undefined" && requested.length > 0) {
// Browsers derive the default PDF filename from document.title.
document.title = requested;
}
try {
window.print();
} finally {
if (typeof document !== "undefined") {
document.title = previousTitle;
}
}
}