134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import { forceArgs } from "../cli/forceArgs.ts";
|
|
import { selectMenuInteractive } from "../cli/selectMenu.ts";
|
|
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
|
import type { ITool } from "../types.ts";
|
|
import { loadPdf, savePdf } from "util/saveLoadPdf.ts";
|
|
|
|
import { PDFName, type PDFNumber, type PDFWidgetAnnotation } from "pdf-lib";
|
|
import { cliPrompt } from "../cli/prompts.ts";
|
|
|
|
type AcrobatVisibility =
|
|
| "Visible"
|
|
| "Hidden"
|
|
| "VisibleButDoesNotPrint"
|
|
| "HiddenButPrintable";
|
|
|
|
export function getAcrobatVisibility(
|
|
widget: PDFWidgetAnnotation,
|
|
): AcrobatVisibility {
|
|
const raw = widget.dict.lookup(PDFName.of("F")) as PDFNumber;
|
|
const flags = raw?.asNumber?.() ?? 0;
|
|
|
|
const isInvisible = (flags & (1 << 1)) !== 0;
|
|
const isHidden = (flags & (1 << 2)) !== 0;
|
|
const isNoPrint = (flags & (1 << 3)) !== 0;
|
|
const isPrint = (flags & (1 << 2)) === 0 && !isNoPrint;
|
|
|
|
if (isInvisible && isHidden) {
|
|
return isPrint ? "HiddenButPrintable" : "Hidden";
|
|
} else if (isNoPrint) {
|
|
return "VisibleButDoesNotPrint";
|
|
} else {
|
|
return "Visible";
|
|
}
|
|
}
|
|
|
|
export function setAcrobatVisibility(
|
|
widget: PDFWidgetAnnotation,
|
|
visibility: AcrobatVisibility,
|
|
) {
|
|
let flags = 0;
|
|
|
|
switch (visibility) {
|
|
case "Visible":
|
|
// No visibility bits set
|
|
break;
|
|
case "Hidden":
|
|
flags |= 1 << 1; // Invisible
|
|
flags |= 1 << 2; // Hidden
|
|
break;
|
|
case "VisibleButDoesNotPrint":
|
|
flags |= 1 << 3; // NoPrint
|
|
break;
|
|
case "HiddenButPrintable":
|
|
flags |= 1 << 1; // Invisible
|
|
flags |= 1 << 2; // Hidden
|
|
flags |= 1 << 3; // NoPrint — UNset this to allow print
|
|
break;
|
|
}
|
|
|
|
widget.dict.set(PDFName.of("F"), widget.dict.context.obj(flags));
|
|
}
|
|
|
|
export class FieldVisibility implements ITool {
|
|
name = "Field Visibility";
|
|
description = "Change visibility of fields";
|
|
block?: TerminalBlock;
|
|
async run(pdfPath: string) {
|
|
if (!this.block) this.block = new TerminalBlock();
|
|
this.block.setPreserveHistory(false);
|
|
[pdfPath] = await forceArgs([pdfPath], [[
|
|
"Please provide path to PDF:",
|
|
(e) => e?.endsWith(".pdf"),
|
|
]], this.block);
|
|
|
|
const pdf = await loadPdf(pdfPath);
|
|
const form = pdf.getForm();
|
|
const fields = form.getFields();
|
|
|
|
let changesMade = false;
|
|
|
|
while (true) {
|
|
const fieldAndVisibility = await selectMenuInteractive(
|
|
`Select a field to change visibility (ESC to ${
|
|
changesMade ? "continue" : "cancel"
|
|
})`,
|
|
fields.flatMap((f) => {
|
|
const name = f.getName();
|
|
const visibility = f.acroField.getWidgets().map((w, i, a) =>
|
|
`${name}${a.length > 1 ? "#" + i : ""} :: ${
|
|
getAcrobatVisibility(w)
|
|
}`
|
|
);
|
|
return visibility;
|
|
}),
|
|
);
|
|
if (!fieldAndVisibility) break;
|
|
const visibility = await selectMenuInteractive(
|
|
fieldAndVisibility,
|
|
[
|
|
"Visible",
|
|
"Hidden",
|
|
"HiddenButPrintable",
|
|
"VisibleButDoesNotPrint",
|
|
] as AcrobatVisibility[],
|
|
) as AcrobatVisibility | null;
|
|
if (!visibility) continue;
|
|
|
|
const [fName, widgetIndex] = fieldAndVisibility.split("::")[0].trim()
|
|
.split("#");
|
|
const field = fields.find((f) => f.getName() === fName);
|
|
if (!field) break;
|
|
|
|
const widget = field.acroField.getWidgets()[Number(widgetIndex) || 0];
|
|
setAcrobatVisibility(widget, visibility);
|
|
changesMade = true;
|
|
}
|
|
|
|
if (changesMade) {
|
|
const path = await cliPrompt(
|
|
"Save to path (or hit enter to keep current):",
|
|
this.block,
|
|
) || pdfPath;
|
|
savePdf(pdf, path);
|
|
}
|
|
}
|
|
help?: (() => Promise<void> | void) | undefined;
|
|
done?: (() => Promise<void> | void) | undefined;
|
|
setBlock(block: TerminalBlock) {
|
|
this.block = block;
|
|
}
|
|
}
|
|
|
|
export default new FieldVisibility();
|