Skip to content

Enhance PDF handling and update dependencies#40

Merged
sumitsahoo merged 11 commits intomainfrom
dev
Apr 10, 2026
Merged

Enhance PDF handling and update dependencies#40
sumitsahoo merged 11 commits intomainfrom
dev

Conversation

@sumitsahoo
Copy link
Copy Markdown
Owner

This pull request updates the project's PDF manipulation library to use @pdfme/pdf-lib in place of pdf-lib, and makes several dependency upgrades throughout the codebase. It also updates documentation to reflect the new library and adds a mention of file attachment features.

PDF Library Migration:

  • Replaces pdf-lib with @pdfme/pdf-lib as the project's PDF manipulation library in both package.json and pnpm-lock.yaml, and updates all related documentation references. This impacts all PDF operations such as merging, splitting, rotation, watermarking, and more. [1] [2] [3] [4] [5] [6]

Dependency Upgrades:

  • Updates several dependencies to their latest versions, including lucide-react, react, react-dom, and vite, as well as various transitive dependencies in pnpm-lock.yaml such as baseline-browser-mapping, call-bind, caniuse-lite, electron-to-chromium, defu, es-abstract, lru-cache, postcss, side-channel-list, tinyexec, and tinyglobby. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19]

Documentation Improvements:

  • Adds a new feature row for "File Attachments" (view, add, extract, or remove embedded files) in the feature list in README.md.
  • Updates documentation to refer to @pdfme/pdf-lib instead of the old pdf-lib package, ensuring consistency and clarity for users and contributors. [1] [2]

New Transitive Dependencies:

  • Introduces new dependencies required by @pdfme/pdf-lib, such as node-html-better-parser and pako@2.1.0, as reflected in the lockfile. [1] [2] [3]

These changes ensure the project uses a maintained and compatible PDF library, improve feature clarity, and keep the dependency tree up to date.

Copilot AI review requested due to automatic review settings April 10, 2026 08:47
@sumitsahoo sumitsahoo merged commit 14c531e into main Apr 10, 2026
6 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the app’s PDF manipulation layer from pdf-lib to @pdfme/pdf-lib, upgrades several key dependencies (React, lucide-react, Vite+ core), and introduces new PDF capabilities (embedded file attachments and a rectangle/badge stamp style) with corresponding UI and documentation updates.

Changes:

  • Replace pdf-lib imports/usages with @pdfme/pdf-lib across utilities and tools, plus dependency upgrades in package.json/pnpm-lock.yaml.
  • Add a new “File Attachments” tool (view/add/extract/remove embedded files) backed by new PDF operations.
  • Extend the Stamp tool with a new “rectangle/badge” mode and refresh inspector metadata icons + minor drag UX tweaks.

Reviewed changes

Copilot reviewed 18 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/utils/pdf-security.ts Switch low-level PDF object imports to @pdfme/pdf-lib.
src/utils/pdf-operations.ts Migrate PDF lib import; adjust metadata loading; add attachment operations and rectangle stamp implementation.
src/types.ts Add new tool id "file-attachment".
src/tools/StampPdf.tsx Add rectangle/badge stamp mode and migrate dynamic import to @pdfme/pdf-lib.
src/tools/PdfInspector.tsx Update metadata row icons for clearer labeling.
src/tools/HeaderFooter.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/FillPdfForm.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/FileAttachment.tsx New tool UI for listing/adding/extracting/removing PDF embedded files.
src/tools/CropPages.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/ContactSheet.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/BatesNumbering.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/AddSignature.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/AddPageNumbers.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/tools/AddBookmarks.tsx Migrate dynamic import to @pdfme/pdf-lib.
src/hooks/useSortableDrag.ts Add iOS touch-callout/user-select prevention styles for drag UX.
src/App.tsx Register new File Attachments tool and route/component mapping.
README.md Document library migration and list File Attachments as a feature.
public/icons/og-image.svg Update OG image layout/text for marketing preview.
package.json Replace pdf-lib with @pdfme/pdf-lib and bump core deps.
pnpm-lock.yaml Lockfile updates reflecting new/updated dependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +733 to 740
const pdf = await PDFDocument.load(arrayBuffer, { updateMetadata: false });

pdf.setTitle(metadata.title);
pdf.setAuthor(metadata.author);
pdf.setSubject(metadata.subject);
pdf.setKeywords(metadata.keywords.split(",").map((k) => k.trim()));
pdf.setKeywords([metadata.keywords]);
pdf.setCreator(metadata.creator);
pdf.setProducer(metadata.producer);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setPdfMetadata now calls pdf.setKeywords([metadata.keywords]), but the UI describes this field as “Comma-separated keywords” (see EditMetadata tool) and the previous behavior split/trimmed the string. As written, a user-entered "a, b" will be stored as a single keyword value including the comma. Consider restoring comma-splitting (with trimming and empty filtering) or otherwise aligning storage format with the UI and getPdfMetadata expectations.

Copilot uses AI. Check for mistakes.
Comment on lines +1895 to +1902
if (!stream || !("getContents" in stream)) continue;

const data = (stream as unknown as { getContents(): Uint8Array }).getContents();
const streamDict = stream as unknown as PDFDict;
const paramsDict = streamDict.lookup?.(PDFName.of("Params"), PDFDict);
const sizeNum = paramsDict?.lookup(PDFName.of("Size"), PDFNumber);

const subtypeObj = streamDict.lookup?.(PDFName.of("Subtype"), PDFName);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In listPdfAttachments, stream is treated as if it were a PDFDict (cast to PDFDict and then lookup is called). For embedded files, the /EF entry typically points to a stream object (e.g. PDFRawStream/PDFStream) whose dictionary is accessed via stream.dict (see how streams are handled in pdf-security.ts). As written, streamDict.lookup?.(...) will likely be undefined at runtime, and MIME type / size extraction will fail. Consider narrowing stream to the proper stream type and reading Params/Subtype from its stream dictionary.

Suggested change
if (!stream || !("getContents" in stream)) continue;
const data = (stream as unknown as { getContents(): Uint8Array }).getContents();
const streamDict = stream as unknown as PDFDict;
const paramsDict = streamDict.lookup?.(PDFName.of("Params"), PDFDict);
const sizeNum = paramsDict?.lookup(PDFName.of("Size"), PDFNumber);
const subtypeObj = streamDict.lookup?.(PDFName.of("Subtype"), PDFName);
if (
!stream ||
!("getContents" in stream) ||
!("dict" in stream) ||
!(stream.dict instanceof PDFDict)
) {
continue;
}
const embeddedFileStream = stream as unknown as {
getContents(): Uint8Array;
dict: PDFDict;
};
const data = embeddedFileStream.getContents();
const streamDict = embeddedFileStream.dict;
const paramsDict = streamDict.lookup(PDFName.of("Params"), PDFDict);
const sizeNum = paramsDict?.lookup(PDFName.of("Size"), PDFNumber);
const subtypeObj = streamDict.lookup(PDFName.of("Subtype"), PDFName);

Copilot uses AI. Check for mistakes.
Comment on lines +1858 to +1866
const namesDict = catalog.lookup(PDFName.of("Names"));
if (!(namesDict instanceof PDFDict)) return [];

const efDict = namesDict.lookup(PDFName.of("EmbeddedFiles"));
if (!(efDict instanceof PDFDict)) return [];

const namesArray = efDict.lookup(PDFName.of("Names"));
if (!(namesArray instanceof PDFArray)) return [];

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listPdfAttachments assumes the EmbeddedFiles name tree is always represented as a flat /Names array (/Names -> /EmbeddedFiles -> /Names). Per the PDF spec, name trees can also be represented with /Kids (and /Limits), in which case this will incorrectly return an empty list even when attachments exist. Consider implementing a small recursive name-tree traversal that handles both the /Names leaf form and the /Kids internal-node form.

Copilot uses AI. Check for mistakes.
Comment on lines +1963 to +1972
const keepIndices: number[] = [];
for (let i = 0; i < namesArray.size(); i += 2) {
const nameObj = namesArray.lookup(i);
const name =
nameObj instanceof PDFString
? nameObj.decodeText()
: nameObj instanceof PDFName
? nameObj.decodeText()
: "";
if (!namesToRemove.has(name)) {
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeAttachmentsFromPdf matches entries only against the name-tree key (nameObj). However, listPdfAttachments prefers the filespec’s /UF or /F when presenting the attachment name to the user. If the displayed name differs from the name-tree key, the UI may pass a name that this removal logic can never match, so removal will silently do nothing. Consider using the same name-resolution logic as listPdfAttachments (and/or matching against both the tree key and /UF//F).

Suggested change
const keepIndices: number[] = [];
for (let i = 0; i < namesArray.size(); i += 2) {
const nameObj = namesArray.lookup(i);
const name =
nameObj instanceof PDFString
? nameObj.decodeText()
: nameObj instanceof PDFName
? nameObj.decodeText()
: "";
if (!namesToRemove.has(name)) {
const decodePdfText = (value: unknown): string => {
if (value instanceof PDFString || value instanceof PDFName) {
return value.decodeText();
}
return "";
};
const keepIndices: number[] = [];
for (let i = 0; i < namesArray.size(); i += 2) {
const nameObj = namesArray.lookup(i);
const fileSpecObj = namesArray.lookup(i + 1);
const candidateNames = new Set<string>();
const treeKeyName = decodePdfText(nameObj);
if (treeKeyName) {
candidateNames.add(treeKeyName);
}
if (fileSpecObj instanceof PDFDict) {
const unicodeFileName = decodePdfText(fileSpecObj.lookup(PDFName.of("UF")));
const fileName = decodePdfText(fileSpecObj.lookup(PDFName.of("F")));
if (unicodeFileName) {
candidateNames.add(unicodeFileName);
}
if (fileName) {
candidateNames.add(fileName);
}
}
const shouldRemove = Array.from(candidateNames).some((name) =>
namesToRemove.has(name),
);
if (!shouldRemove) {

Copilot uses AI. Check for mistakes.
Comment on lines +1954 to +1962
const namesDict = catalog.lookup(PDFName.of("Names"));
if (!(namesDict instanceof PDFDict)) return pdf.save();

const efDict = namesDict.lookup(PDFName.of("EmbeddedFiles"));
if (!(efDict instanceof PDFDict)) return pdf.save();

const namesArray = efDict.lookup(PDFName.of("Names"));
if (!(namesArray instanceof PDFArray)) return pdf.save();

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeAttachmentsFromPdf only supports the leaf form of the EmbeddedFiles name tree (/Names array). If a PDF uses the internal-node form with /Kids (and /Limits), this function won’t find or remove attachments. Consider reusing a shared name-tree traversal helper so both listing and removal can handle /Kids as well as /Names.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants