72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import type { DocPageSetup } from "@/lib/drive/doc-page-setup"
|
|
|
|
type DocxArchive = Record<string, Uint8Array>
|
|
|
|
const TWIPS_PER_MM = 1440 / 25.4
|
|
|
|
function mmToTwips(mm: number): number {
|
|
return Math.round(mm * TWIPS_PER_MM)
|
|
}
|
|
|
|
function decodeXml(bytes: Uint8Array | undefined): string {
|
|
if (!bytes) return ""
|
|
return new TextDecoder().decode(bytes)
|
|
}
|
|
|
|
function encodeXml(text: string): Uint8Array {
|
|
return new TextEncoder().encode(text)
|
|
}
|
|
|
|
function patchSectPr(documentXml: string, pageSetup: DocPageSetup): string {
|
|
const w = mmToTwips(pageSetup.widthMm)
|
|
const h = mmToTwips(pageSetup.heightMm)
|
|
const orient =
|
|
pageSetup.orientation === "landscape" || pageSetup.widthMm > pageSetup.heightMm
|
|
? ' w:orient="landscape"'
|
|
: ""
|
|
|
|
const top = mmToTwips(pageSetup.marginsMm.top)
|
|
const right = mmToTwips(pageSetup.marginsMm.right)
|
|
const bottom = mmToTwips(pageSetup.marginsMm.bottom)
|
|
const left = mmToTwips(pageSetup.marginsMm.left)
|
|
const header = mmToTwips(pageSetup.headerMarginMm ?? pageSetup.marginsMm.top)
|
|
const footer = mmToTwips(pageSetup.footerMarginMm ?? pageSetup.marginsMm.bottom)
|
|
|
|
const pgSz = `<w:pgSz w:w="${w}" w:h="${h}"${orient}/>`
|
|
const pgMar = `<w:pgMar w:top="${top}" w:right="${right}" w:bottom="${bottom}" w:left="${left}" w:header="${header}" w:footer="${footer}" w:gutter="0"/>`
|
|
|
|
if (/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>/i.test(documentXml)) {
|
|
return documentXml.replace(/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>/i, (match) => {
|
|
let inner = match
|
|
.replace(/<w:pgSz\b[^>]*\/?>/gi, "")
|
|
.replace(/<w:pgMar\b[^>]*\/?>/gi, "")
|
|
inner = inner.replace("</w:sectPr>", `${pgSz}${pgMar}</w:sectPr>`)
|
|
return inner
|
|
})
|
|
}
|
|
|
|
if (/<w:sectPr\b[^>]*\/>/i.test(documentXml)) {
|
|
return documentXml.replace(
|
|
/<w:sectPr\b[^>]*\/>/i,
|
|
`<w:sectPr>${pgSz}${pgMar}</w:sectPr>`
|
|
)
|
|
}
|
|
|
|
return documentXml.replace(/<\/w:body>/i, `<w:sectPr>${pgSz}${pgMar}</w:sectPr></w:body>`)
|
|
}
|
|
|
|
/** Patch page size and margins in a generated DOCX buffer. */
|
|
export async function patchDocxPageSetup(
|
|
buffer: ArrayBuffer | Uint8Array,
|
|
pageSetup: DocPageSetup
|
|
): Promise<Uint8Array> {
|
|
const { unzipSync, zipSync } = await import("fflate")
|
|
const archive = { ...(unzipSync(new Uint8Array(buffer)) as DocxArchive) }
|
|
const documentPath = "word/document.xml"
|
|
const xml = decodeXml(archive[documentPath])
|
|
if (!xml) return new Uint8Array(buffer)
|
|
|
|
archive[documentPath] = encodeXml(patchSectPr(xml, pageSetup))
|
|
return zipSync(archive)
|
|
}
|