feat: esc to cancel select menus
feat: fieldVisibility tool
This commit is contained in:
@@ -4,7 +4,6 @@ import { multiSelectMenuInteractive } from "../cli/selectMenu.ts";
|
||||
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
||||
import type { callback, ITool } from "../types.ts";
|
||||
import { loadPdf, savePdf } from "util/saveLoadPdf.ts";
|
||||
import { log } from "util/logfile.ts";
|
||||
|
||||
export class DeleteFormFields implements ITool {
|
||||
name = "deleteFormFields";
|
||||
|
133
tools/fieldVisibility.ts
Normal file
133
tools/fieldVisibility.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
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();
|
Reference in New Issue
Block a user