updeted
This commit is contained in:
@@ -6,6 +6,7 @@ import { LeaveRequest, LeaveRequestStatus } from "@/types/hrm"
|
||||
export interface ListLeaveRequestsParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
q?: string
|
||||
employeeId?: number
|
||||
status?: LeaveRequestStatus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Minimal single-page PDF writer — no dependency (jspdf/pdf-lib) needed.
|
||||
// Builds a valid PDF byte-for-byte using the base14 Helvetica font, which every
|
||||
// PDF reader supports without embedding. Good enough for text/table documents
|
||||
// like a payslip breakdown; not a general-purpose PDF library.
|
||||
|
||||
export interface PdfLine {
|
||||
text: string
|
||||
x: number
|
||||
y: number
|
||||
size?: number
|
||||
bold?: boolean
|
||||
color?: [number, number, number] // 0-1 RGB
|
||||
}
|
||||
|
||||
const PAGE_WIDTH = 612 // US Letter, points
|
||||
const PAGE_HEIGHT = 792
|
||||
|
||||
function escapePdfText(s: string): string {
|
||||
return s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")
|
||||
}
|
||||
|
||||
/** Builds a one-page PDF from absolutely-positioned text lines (origin bottom-left, like PDF space). */
|
||||
export function buildSimplePdf(lines: PdfLine[]): Blob {
|
||||
const content: string[] = ["q"]
|
||||
let currentFont: "F1" | "F2" | null = null
|
||||
let currentSize: number | null = null
|
||||
let currentColor: string | null = null
|
||||
|
||||
for (const line of lines) {
|
||||
const font = line.bold ? "F2" : "F1"
|
||||
const size = line.size ?? 11
|
||||
const [r, g, b] = line.color ?? [0.07, 0.07, 0.07]
|
||||
const colorKey = `${r},${g},${b}`
|
||||
|
||||
if (colorKey !== currentColor) {
|
||||
content.push(`${r.toFixed(3)} ${g.toFixed(3)} ${b.toFixed(3)} rg`)
|
||||
currentColor = colorKey
|
||||
}
|
||||
content.push("BT")
|
||||
if (font !== currentFont || size !== currentSize) {
|
||||
content.push(`/${font} ${size} Tf`)
|
||||
currentFont = font
|
||||
currentSize = size
|
||||
}
|
||||
content.push(`${line.x.toFixed(2)} ${line.y.toFixed(2)} Td`)
|
||||
content.push(`(${escapePdfText(line.text)}) Tj`)
|
||||
content.push("ET")
|
||||
}
|
||||
content.push("Q")
|
||||
const contentStream = content.join("\n")
|
||||
|
||||
const objects: string[] = []
|
||||
objects.push("<< /Type /Catalog /Pages 2 0 R >>")
|
||||
objects.push("<< /Type /Pages /Kids [3 0 R] /Count 1 >>")
|
||||
objects.push(
|
||||
`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}] /Resources << /Font << /F1 4 0 R /F2 5 0 R >> >> /Contents 6 0 R >>`
|
||||
)
|
||||
objects.push("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||||
objects.push("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>")
|
||||
objects.push(`<< /Length ${contentStream.length} >>\nstream\n${contentStream}\nendstream`)
|
||||
|
||||
let pdf = "%PDF-1.4\n"
|
||||
const offsets: number[] = [0]
|
||||
objects.forEach((obj, i) => {
|
||||
offsets.push(pdf.length)
|
||||
pdf += `${i + 1} 0 obj\n${obj}\nendobj\n`
|
||||
})
|
||||
|
||||
const xrefStart = pdf.length
|
||||
let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`
|
||||
for (let i = 1; i <= objects.length; i++) {
|
||||
xref += `${offsets[i].toString().padStart(10, "0")} 00000 n \n`
|
||||
}
|
||||
pdf += xref
|
||||
pdf += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefStart}\n%%EOF`
|
||||
|
||||
return new Blob([pdf], { type: "application/pdf" })
|
||||
}
|
||||
|
||||
export function downloadPdf(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = filename
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
Reference in New Issue
Block a user