Revert "v2.39.0 (#2180)"

This reverts commit 1e228c86c3.
This commit is contained in:
Raktima
2026-05-25 17:24:36 +05:30
committed by GitHub
parent 1e228c86c3
commit 7b4d0bf052
92 changed files with 5372 additions and 8680 deletions
+33 -492
View File
@@ -2,15 +2,7 @@ import dotenv from 'dotenv';
import { format, toZonedTime } from 'date-fns-tz';
import getPresignedUrl, { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
import crypto from 'node:crypto';
import {
PDFDocument,
PDFName,
rgb,
degrees,
// StandardFonts,
PDFArray,
PDFDict,
} from 'pdf-lib';
import { PDFDocument, rgb } from 'pdf-lib';
import { parseUploadFile } from './utils/fileUtils.js';
dotenv.config({ quiet: true });
@@ -157,484 +149,41 @@ export function generateId(length) {
}
/**
* FlattenPdf renders field values as static content and removes the interactive
* form layer. Signatures are stripped entirely. Non-widget annotations (links,
* comments, stamps) are preserved.
* FlattenPdf is used to remove existing widgets if present any and flatten pdf.
* @param {string | Uint8Array | ArrayBuffer} pdfFile - pdf file.
* @returns {Promise<Uint8Array>} flatPdf - pdf file in Uint8Array
* @returns {Promise<Uint8Array>} flatPdf - pdf file in unit8arry
*/
export const flattenPdf = async pdfFile => {
const pdfDoc = await PDFDocument.load(pdfFile, { ignoreEncryption: true });
try {
const acroFormEntry = pdfDoc.catalog.get(PDFName.of('AcroForm'));
const acroForm = pdfDoc.context.lookupMaybe
? pdfDoc.context.lookupMaybe(acroFormEntry)
: pdfDoc.context.lookup(acroFormEntry);
if (acroForm && typeof acroForm.set === 'function') {
// Avoid pdf-lib form APIs here; some malformed PDFs crash while
// iterating/removing fields. Clearing /Fields directly is safer.
acroForm.set(PDFName.of('Fields'), pdfDoc.context.obj([]));
acroForm.delete(PDFName.of('XFA'));
acroForm.delete(PDFName.of('SigFlags'));
}
} catch {
// If AcroForm is malformed, continue with page annotation cleanup.
}
for (const page of pdfDoc.getPages()) {
try {
const annotationsRef = page.node.get(PDFName.of('Annots'));
if (!annotationsRef) continue;
const annotations = pdfDoc.context.lookup(annotationsRef);
if (!annotations || !annotations.asArray) continue;
const filtered = annotations.asArray().filter(annotRef => {
try {
const annot = pdfDoc.context.lookup(annotRef);
const subtype = annot?.get(PDFName.of('Subtype'));
return subtype?.toString() !== '/Widget';
} catch {
return true;
}
});
if (filtered.length === 0) {
page.node.delete(PDFName.of('Annots'));
} else {
page.node.set(PDFName.of('Annots'), pdfDoc.context.obj(filtered));
}
} catch {
// best effort cleanup
}
}
try {
pdfDoc.catalog.delete(PDFName.of('AcroForm'));
} catch {
// best effort cleanup
}
return await pdfDoc.save({ useObjectStreams: false });
};
/* ---- flattenPdf private helpers ---- */
function _safeGetWidgetsServer(field) {
try {
return field.acroField?.getWidgets?.() || [];
} catch {
return [];
}
}
function _getWidgetRectServer(widget, pdfDoc) {
try {
const r = widget.getRectangle?.();
if (r && isFinite(r.x) && isFinite(r.y) && isFinite(r.width) && isFinite(r.height)) {
return r;
}
} catch {
/* fall through to manual extraction */
}
try {
const rectArr = widget.dict?.lookup?.(PDFName.of('Rect'));
if (!rectArr || typeof rectArr.size !== 'function' || rectArr.size() !== 4) return null;
const x1 = _numberFromPdfObjectServer(rectArr.get(0));
const y1 = _numberFromPdfObjectServer(rectArr.get(1));
const x2 = _numberFromPdfObjectServer(rectArr.get(2));
const y2 = _numberFromPdfObjectServer(rectArr.get(3));
if ([x1, y1, x2, y2].some(v => !isFinite(v))) return null;
return {
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
};
} catch {
return null;
}
}
function _numberFromPdfObjectServer(obj) {
if (!obj) return NaN;
if (typeof obj.asNumber === 'function') return obj.asNumber();
if (typeof obj.numberValue === 'function') return obj.numberValue();
return Number(obj?.value ?? obj);
}
function _getWidgetPageServer(pdfDoc, pages, widget) {
try {
const pRef = widget.P?.();
if (pRef) {
for (const page of pages) {
if (page.ref === pRef) return page;
}
}
} catch {
/* fall through */
}
try {
const pRef = widget.dict?.get?.(PDFName.of('P'));
if (pRef) {
for (const page of pages) {
if (page.ref === pRef) return page;
}
}
} catch {
/* fall through */
}
// Fallback: search page annots
try {
for (const page of pages) {
const annots = page.node.lookupMaybe(PDFName.of('Annots'), PDFArray);
if (!annots) continue;
for (let i = 0; i < annots.size(); i++) {
const ref = annots.get(i);
if (ref === widget.ref) return page;
}
}
} catch {
/* fall through */
}
return null;
}
function _drawWidgetBoxServer(page, rect) {
try {
page.drawRectangle({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
borderWidth: 0.6,
borderColor: rgb(0.65, 0.65, 0.65),
});
} catch {
/* best effort */
}
}
function _drawTextFieldServer(page, field, rect, font) {
let text = '';
try {
text = field.getText?.() ?? '';
} catch {
text = '';
}
text = String(text ?? '');
if (!text) return;
let multiline = false;
try {
multiline = field.isMultiline?.() ?? false;
} catch {
/* ignore */
}
let comb = false;
try {
comb = field.isCombed?.() ?? false;
} catch {
/* ignore */
}
if (comb) {
_drawCombTextServer(page, text, rect, font);
return;
}
if (multiline || text.includes('\n')) {
_drawMultilineTextServer(page, text, rect, font);
return;
}
const fontSize = _fitSingleLineFontSizeServer(text, rect, font);
const baselineY = rect.y + Math.max(2, (rect.height - fontSize) / 2);
page.drawText(text, {
x: rect.x + 2,
y: baselineY,
size: fontSize,
font,
color: rgb(0, 0, 0),
maxWidth: Math.max(1, rect.width - 4),
});
}
function _drawMultilineTextServer(page, text, rect, font) {
const lines = String(text).replace(/\r/g, '').split('\n');
const fontSize = Math.max(8, Math.min(11, rect.height / Math.max(lines.length + 0.5, 2)));
const lineHeight = fontSize + 1.5;
let y = rect.y + rect.height - fontSize - 2;
for (const line of lines) {
if (y < rect.y + 1) break;
page.drawText(line, {
x: rect.x + 2,
y,
size: fontSize,
font,
color: rgb(0, 0, 0),
maxWidth: Math.max(1, rect.width - 4),
lineHeight,
});
y -= lineHeight;
}
}
function _drawCombTextServer(page, text, rect, font) {
const chars = String(text).split('');
const count = Math.max(chars.length, 1);
const cellWidth = rect.width / count;
const fontSize = Math.max(8, Math.min(12, rect.height - 4));
chars.forEach((ch, i) => {
const textWidth = font.widthOfTextAtSize(ch, fontSize);
const x = rect.x + i * cellWidth + (cellWidth - textWidth) / 2;
const y = rect.y + Math.max(2, (rect.height - fontSize) / 2);
page.drawText(ch, {
x,
y,
size: fontSize,
font,
color: rgb(0, 0, 0),
});
if (i < count - 1) {
const pdfDoc = await PDFDocument.load(pdfFile);
// Get the form
const form = pdfDoc.getForm();
// fetch form fields
const fields = form.getFields();
// remove form all existing fields and their widgets
if (fields && fields?.length > 0) {
try {
page.drawLine({
start: { x: rect.x + (i + 1) * cellWidth, y: rect.y },
end: { x: rect.x + (i + 1) * cellWidth, y: rect.y + rect.height },
thickness: 0.4,
color: rgb(0.75, 0.75, 0.75),
});
} catch {
/* best effort */
}
}
});
}
function _fitSingleLineFontSizeServer(text, rect, font) {
let size = Math.min(12, rect.height - 4);
size = Math.max(size, 6);
while (size > 6) {
const width = font.widthOfTextAtSize(text, size);
if (width <= rect.width - 4) return size;
size -= 0.5;
}
return 6;
}
function _drawCheckBoxServer(page, field, rect, zapf) {
let checked = false;
try {
checked = field.isChecked();
} catch {
checked = false;
}
if (!checked) return;
const size = Math.max(8, Math.min(rect.width, rect.height) - 4);
page.drawText('\u2714', {
x: rect.x + Math.max(1, (rect.width - size * 0.7) / 2),
y: rect.y + Math.max(1, (rect.height - size) / 2),
size,
font: zapf,
color: rgb(0, 0, 0),
});
}
function _drawRadioGroupServer(page, field, widget, rect) {
let selected = null;
try {
selected = field.getSelected();
} catch {
selected = null;
}
if (!selected) return;
let widgetOnValue = null;
try {
widgetOnValue = widget.getOnValue?.();
} catch {
/* ignore */
}
if (!widgetOnValue) {
try {
const ap = widget.dict?.lookupMaybe?.(PDFName.of('AP'), PDFDict);
const n = ap?.lookupMaybe?.(PDFName.of('N'), PDFDict);
if (n) {
const keys = n.keys();
for (const k of keys) {
const name = k?.decodeText?.() ?? k?.encodedName ?? String(k);
if (name !== '/Off' && name !== 'Off') {
widgetOnValue = name.replace(/^\//, '');
break;
for (const field of fields) {
while (field.acroField.getWidgets().length) {
field.acroField.removeWidget(0);
}
form.removeField(field);
}
} catch (err) {
console.log('err while removing field from pdf', err);
}
} catch {
/* ignore */
}
// Updates the field appearances to ensure visual changes are reflected.
form.updateFieldAppearances();
// Flattens the form, converting all form fields into non-editable, static content
form.flatten();
const flatPdf = await pdfDoc.save({ useObjectStreams: false });
return flatPdf;
} catch (err) {
console.log('err ', err);
throw new Error('error in pdf');
}
const selectedStr = String(selected).replace(/^\//, '');
const onStr = String(widgetOnValue ?? '').replace(/^\//, '');
if (!onStr || selectedStr !== onStr) return;
// Circle outline
try {
page.drawEllipse({
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2,
xScale: rect.width / 2 - 1,
yScale: rect.height / 2 - 1,
borderWidth: 0.8,
borderColor: rgb(0, 0, 0),
});
} catch {
/* best effort */
}
// Inner filled dot
try {
const r = Math.min(rect.width, rect.height) / 4;
page.drawEllipse({
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2,
xScale: r,
yScale: r,
color: rgb(0, 0, 0),
});
} catch {
/* best effort */
}
}
function _drawDropdownServer(page, field, rect, font) {
let text = '';
try {
const selected = field.getSelected?.();
if (Array.isArray(selected)) {
text = selected.join(', ');
} else {
text = selected ?? '';
}
} catch {
text = '';
}
text = String(text ?? '');
if (!text) return;
const fontSize = _fitSingleLineFontSizeServer(text, rect, font);
page.drawText(text, {
x: rect.x + 2,
y: rect.y + Math.max(2, (rect.height - fontSize) / 2),
size: fontSize,
font,
color: rgb(0, 0, 0),
maxWidth: Math.max(1, rect.width - 12),
});
}
function _drawOptionListServer(page, field, rect, font) {
let selected = [];
try {
selected = field.getSelected?.() || [];
} catch {
selected = [];
}
if (!Array.isArray(selected)) {
selected = [selected].filter(Boolean);
}
if (!selected.length) return;
const lines = selected.map(v => String(v));
const fontSize = Math.max(8, Math.min(11, rect.height / Math.max(lines.length + 0.5, 2)));
const lineHeight = fontSize + 1.5;
let y = rect.y + rect.height - fontSize - 2;
for (const line of lines) {
if (y < rect.y + 1) break;
page.drawText(line, {
x: rect.x + 2,
y,
size: fontSize,
font,
color: rgb(0, 0, 0),
maxWidth: Math.max(1, rect.width - 4),
});
y -= lineHeight;
}
}
/** Remove only Widget annotations; preserve links, stamps, comments, etc. */
function _removeWidgetAnnotationsServer(pdfDoc) {
for (const page of pdfDoc.getPages()) {
try {
const annotationsRef = page.node.get(PDFName.of('Annots'));
if (!annotationsRef) continue;
const annotations = pdfDoc.context.lookup(annotationsRef);
if (!annotations || !annotations.asArray) continue;
const filtered = annotations.asArray().filter(annotRef => {
try {
const annot = pdfDoc.context.lookup(annotRef);
const subtype = annot?.get(PDFName.of('Subtype'));
return subtype?.toString() !== '/Widget';
} catch {
return true;
}
});
if (filtered.length === 0) {
page.node.delete(PDFName.of('Annots'));
} else {
page.node.set(PDFName.of('Annots'), pdfDoc.context.obj(filtered));
}
} catch {
/* best effort */
}
}
try {
pdfDoc.catalog.delete(PDFName.of('AcroForm'));
} catch {
/* best effort */
}
}
};
// Format date and time for the selected timezone
export const formatTimeInTimezone = (date, timezone) => {
@@ -647,7 +196,7 @@ export const formatTimeInTimezone = (date, timezone) => {
// `getSecureUrl` is used to return local secure url if local files
export const getSecureUrl = url => {
const fileUrl = new URL(url)?.pathname?.includes('/files/');
const fileUrl = new URL(url)?.pathname?.includes('files');
if (fileUrl) {
try {
const file = getSignedLocalUrl(url);
@@ -739,21 +288,13 @@ export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
: formatTimeInTimezone(date, timeZone);
}
export const randomId = (digit = 8) => {
// 1. Grab a cryptographically-secure 32-bit random value
// Use crypto for stronger randomness
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
const raw = randomBytes[0]; // 0 … 4,294,967,295
// Calculate the min and max for the given digit length
const min = Math.pow(10, digit - 1); // e.g., digit=3 → 100
const max = Math.pow(10, digit) - 1; // e.g., digit=3 → 999
const range = max - min + 1;
// Collapse random value into the range and shift
return min + (raw % range);
export const randomId = () => {
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
const randomValue = randomBytes[0];
const randomDigit = 1000 + (randomValue % 9000);
return randomDigit;
};
export const handleValidImage = async Placeholder => {
const updatedPlaceholders = [];