import { asDataUrl, firstValue, normalizeColor, readPositiveNumber, toStr } from './pdf-shared.utils'; const DEFAULT_PAGE_ORIENTATION: 'portrait' | 'landscape' = 'portrait'; const A4_WIDTH = 595.28; const A4_HEIGHT = 841.89; const HALF_A4_HEIGHT = A4_HEIGHT / 2; const DEFAULT_ORG_NAME_FONT_SIZE = 16; const DEFAULT_ORG_ADDRESS_FONT_SIZE = 9; const DEFAULT_ORG_NAME_FONT_COLOR = '#111827'; const DEFAULT_ORG_ADDRESS_FONT_COLOR = '#111827'; const DEFAULT_PAGE_MARGINS: [number, number, number, number] = [20, 20, 20, 20]; const DEFAULT_HEADER_POSITION = 'Left'; const DEFAULT_LOGO_POSITION = 'Left'; export function resolvePageOrientation(value: any): 'portrait' | 'landscape' { return value?.toLowerCase() === 'landscape' ? 'landscape' : DEFAULT_PAGE_ORIENTATION; } export function resolvePageSize( value: any, orientation: 'portrait' | 'landscape' = DEFAULT_PAGE_ORIENTATION ): 'A4' | 'LETTER' | 'LEGAL' | { width: number; height: number } { const raw = String(value ?? '').trim().toLowerCase(); const normalized = raw.replace(/[\s_-]/g, ''); if (normalized === 'halfa4') { // return { width: A4_WIDTH, height: HALF_A4_HEIGHT }; return 'A4'; } if (normalized === 'letter') { return 'LETTER'; } if (normalized === 'legal') { return 'LEGAL'; } return 'A4'; } export function deriveHeaderDataFromInvoice(PDFInvoiceData: any) { const entity = PDFInvoiceData?.Entity || {}; return { invoiceHeading: PDFInvoiceData?.Type ? `${PDFInvoiceData.Type} Invoice` : 'INVOICE', organization: { cName: entity?.CName || '', tLine: entity?.TLine || '', address1: entity?.Adrs1 || entity?.Addr1 || '', address2: entity?.Adrs2 || entity?.Addr2 || '', city: entity?.City || '', state: entity?.State || entity?.St || '', pin: entity?.PIN || entity?.Pin || '', gstin: entity?.GSTIN || entity?.GSTNo || '', gst: entity?.GST || '', cst: entity?.CST || '', cin: entity?.CIN || '', pan: entity?.PAN || '', dlNo: entity?.DLNo || entity?.DLNO || '', email: entity?.Email || entity?.EMail || '', phone: entity?.Phone || entity?.Ph || '', phone2: entity?.Phone2 || entity?.Ph2 || '' } }; } export function normalizeHeaderData(headerData: any) { const organization = headerData?.organization ?? {}; return { logoDataUrl: asDataUrl(headerData?.logoDataUrl), invoiceHeading: headerData?.invoiceHeading ? String(headerData.invoiceHeading) : 'INVOICE', organization: { cName: toStr(organization?.cName), tLine: toStr(organization?.tLine), address1: toStr(organization?.address1), address2: toStr(organization?.address2), city: toStr(organization?.city), state: toStr(organization?.state), pin: toStr(organization?.pin), gstin: toStr(organization?.gstin), gst: toStr(organization?.gst), cst: toStr(organization?.cst), cin: toStr(organization?.cin), pan: toStr(organization?.pan), dlNo: toStr(organization?.dlNo), email: toStr(organization?.email), phone: toStr(organization?.phone), phone2: toStr(organization?.phone2), taxIds: normalizeTaxIds(organization?.taxIds) } }; } export function normalizeHeaderStyle(headerStyles: any) { return { OrgNameFsize: readPositiveNumber(headerStyles?.orgNameFontSize ?? headerStyles?.OrgNameFsize, DEFAULT_ORG_NAME_FONT_SIZE), OrgNameClr: normalizeColor(headerStyles?.orgNameFontColor ?? headerStyles?.OrgNameClr, DEFAULT_ORG_NAME_FONT_COLOR), AdrsFsize: readPositiveNumber(headerStyles?.orgAddressFontSize ?? headerStyles?.AdrsFsize, DEFAULT_ORG_ADDRESS_FONT_SIZE), AdrsClr: normalizeColor(headerStyles?.orgAddressFontColor ?? headerStyles?.AdrsClr, DEFAULT_ORG_ADDRESS_FONT_COLOR) }; } export function resolvePageMargins(value: any): [number, number, number, number] { if (Array.isArray(value) && value.length === 4) { return [ readPositiveNumber(value[0], DEFAULT_PAGE_MARGINS[0]), readPositiveNumber(value[1], DEFAULT_PAGE_MARGINS[1]), readPositiveNumber(value[2], DEFAULT_PAGE_MARGINS[2]), readPositiveNumber(value[3], DEFAULT_PAGE_MARGINS[3]) ]; } if (value && typeof value === 'object') { const left = readPositiveNumber(value?.Left ?? value?.left, DEFAULT_PAGE_MARGINS[0]); const top = readPositiveNumber(value?.Top ?? value?.top, DEFAULT_PAGE_MARGINS[1]); const right = readPositiveNumber(value?.Right ?? value?.right, DEFAULT_PAGE_MARGINS[2]); const bottom = readPositiveNumber(value?.Bottom ?? value?.bottom, DEFAULT_PAGE_MARGINS[3]); return [left, top, right, bottom]; } return DEFAULT_PAGE_MARGINS; } export function buildDocumentHeaderSection(PrintConfig: any, DocumentData: any = {}) { const entity = DocumentData?.Entity ?? {}; const documentLogoDataUrl = resolveDocumentLogoDataUrl(DocumentData); const headerData = normalizeHeaderData(PrintConfig?.headerData ?? {}); const mergedHeaderData = normalizeHeaderData({ ...headerData, logoDataUrl: firstValue(documentLogoDataUrl, headerData?.logoDataUrl), organization: { ...(headerData?.organization ?? {}), cName: firstValue(entity?.CName, headerData?.organization?.cName), tLine: firstValue(entity?.TLine, headerData?.organization?.tLine), address1: firstValue(entity?.Adrs1, headerData?.organization?.address1), address2: firstValue(entity?.Adrs2, headerData?.organization?.address2), dlNo: firstValue(entity?.DLNo, entity?.DLNO, headerData?.organization?.dlNo), gstin: firstValue(entity?.GSTIN, headerData?.organization?.gstin), pan: firstValue(entity?.PAN, headerData?.organization?.pan), taxIds: Array.isArray(entity?.TaxIds) ? entity.TaxIds : headerData?.organization?.taxIds, city: firstValue(entity?.City, headerData?.organization?.city), state: firstValue(entity?.State, headerData?.organization?.state), pin: firstValue(entity?.PIN, entity?.Pin, headerData?.organization?.pin), email: firstValue(entity?.Email, entity?.EMail, headerData?.organization?.email), phone: firstValue(entity?.Phone, entity?.Ph, headerData?.organization?.phone), phone2: firstValue(entity?.Phone2, entity?.Ph2, headerData?.organization?.phone2) } }); const headerStyles = normalizeHeaderStyle(PrintConfig?.headerStyles ?? { OrgNameFsize: PrintConfig?.OrgNameFsize, OrgNameClr: PrintConfig?.OrgNameClr, AdrsFsize: PrintConfig?.AdrsFsize, AdrsClr: PrintConfig?.AdrsClr }); const headerSettings: any = normalizeHeaderSettings(PrintConfig?.Header ?? PrintConfig?.Header ?? {}); if (!headerSettings.Show) { return []; } const hasPrintedTaxIds = !!buildPrintedTaxIdsText(mergedHeaderData.organization?.taxIds); const hasOrganizationData = !!(mergedHeaderData.organization?.cName || mergedHeaderData.organization?.address1 || mergedHeaderData.organization?.address2 || mergedHeaderData.organization?.city || mergedHeaderData.organization?.state || mergedHeaderData.organization?.pin || mergedHeaderData.organization?.gstin || mergedHeaderData.organization?.email || mergedHeaderData.organization?.phone || mergedHeaderData.organization?.phone2 || mergedHeaderData.organization?.dlNo || mergedHeaderData.organization?.cin || mergedHeaderData.organization?.pan || hasPrintedTaxIds); const hasLogo = !!mergedHeaderData.logoDataUrl && headerSettings.ShowLogo; if (!hasOrganizationData && !hasLogo) { return []; } const textAlignment = toPdfAlignment(headerSettings.Position); const builtOrganizationStack = buildOrganizationStack(mergedHeaderData.organization, headerStyles, textAlignment); const organizationStack = Array.isArray(builtOrganizationStack) ? builtOrganizationStack : [{ text: toStr(builtOrganizationStack) }]; const headerContent = buildHeaderContentWithLogo(mergedHeaderData.logoDataUrl, hasLogo, organizationStack, headerSettings.LogoPosition, textAlignment); return [{ columns: [ { width: '*', ...headerContent } ], columnGap: 10, margin: [0, 0, 0, 10] }]; } export function buildDocumentHeadingSection(PDFInvoiceData: any, PrintConfig: any, availableWidth: number) { const headerData = normalizeHeaderData(PrintConfig?.headerData ?? {}); const headingText = PDFInvoiceData.HeaderName || 'INVOICE'; return [ { text: headingText, bold: true, fontSize: 14, alignment: 'center', margin: [0, 0, 0, 4] }, { canvas: [ { type: 'line', x1: 0, y1: 0, x2: Math.max(1, availableWidth), y2: 0, lineWidth: 1, lineColor: '#1f2937' } ], margin: [0, 0, 0, 8] } ]; } export function buildWatermark(PrintConfig: any, DocumentData: any = {}) { const value = DocumentData?.IsProforma ? 'Not a final invoice' : firstValue(PrintConfig?.WatermarkText); const isEnabled = !!(PrintConfig?.Watermark); if (!isEnabled) { return null; } if (!value) { return null; } return { text: value, color: '#9ca3af', opacity: 0.25, bold: true }; } export function buildLogoWatermarkBackground(PrintConfig: any, DocumentData: any = {}) { const value = firstValue(PrintConfig?.WatermarkText); const isEnabled = !!(PrintConfig?.Watermark || PrintConfig?.WatermarkEnabled || value); if (!isEnabled || value) { return null; } const headerData = normalizeHeaderData(PrintConfig?.headerData ?? {}); const logoDataUrl = firstValue(resolveDocumentLogoDataUrl(DocumentData), headerData?.logoDataUrl); if (!logoDataUrl) { return null; } return () => ({ image: logoDataUrl, width: 220, opacity: 0.08, alignment: 'center', margin: [0, 180, 0, 0] }); } function resolveDocumentLogoDataUrl(DocumentData: any) { const entity = DocumentData?.Entity ?? {}; return firstValue( asDataUrl(DocumentData?.logoDataUrl), asDataUrl(DocumentData?.LogoDataUrl), asDataUrl(DocumentData?.Image), asDataUrl(DocumentData?.Logo), asDataUrl(entity?.Image), asDataUrl(entity?.Logo), asDataUrl(entity?.CLogo), asDataUrl(entity?.LogoDataUrl) ); } export function buildFooter(PrintConfig: any) { const footerConfig = PrintConfig?.Footer ?? {}; const showFooter = footerConfig?.Show !== false; if (!showFooter) { return null; } const value = firstValue(footerConfig?.Text ?? PrintConfig?.FooterText); if (!value) { return null; } const textAlignment = normalizeFooterAlignment(footerConfig?.Position ?? footerConfig?.Position); return () => ({ margin: [20, 0, 20, 6], text: value, alignment: textAlignment, fontSize: 8, color: '#4b5563' }); } export function buildPageNumberHeader(PrintConfig: any) { // const showPageNumber = PrintConfig?.Header?.ShowPageNumber !== false; // if (!showPageNumber) { // return null; // } return (currentPage: number, pageCount: number) => ({ text: `Page ${currentPage}/${pageCount}`, alignment: 'right', margin: [20, 4, 20, 0], fontSize: 8, color: '#4b5563' }); } function normalizeFooterAlignment(value: any): 'left' | 'center' | 'right' { const raw = String(value ?? '').trim().toLowerCase(); if (raw === 'center') { return 'center'; } if (raw === 'right') { return 'right'; } return 'left'; } function buildOrganizationStack(organization: any, headerStyles: any, alignment: 'left' | 'center' | 'right') { const stack: any[] = []; const orgNameFontSize = readPositiveNumber(headerStyles?.OrgNameFsize, DEFAULT_ORG_NAME_FONT_SIZE); const orgAddressFontSize = readPositiveNumber(headerStyles?.AdrsFsize, DEFAULT_ORG_ADDRESS_FONT_SIZE); const orgNameFontColor = normalizeColor(headerStyles?.OrgNameClr, DEFAULT_ORG_NAME_FONT_COLOR); const orgAddressFontColor = normalizeColor(headerStyles?.AdrsClr, DEFAULT_ORG_ADDRESS_FONT_COLOR); if (organization?.cName) { stack.push({ text: organization.cName, bold: true, fontSize: orgNameFontSize, color: orgNameFontColor, lineHeight: 1.1, alignment }); } if (organization?.tLine) { stack.push({ text: organization.tLine, bold: true, fontSize: 10, color: orgNameFontColor, lineHeight: 1.1, alignment }); } if (organization?.address1) { stack.push({ text: organization.address1, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.address2) { stack.push({ text: organization.address2, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } const cityStatePin = [organization?.city, organization?.state, organization?.pin].filter((value: string) => !!value).join(', '); if (cityStatePin) { stack.push({ text: cityStatePin, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } const hasTaxIdsConfig = Array.isArray(organization?.taxIds) && organization.taxIds.length > 0; const printedTaxIdsText = buildPrintedTaxIdsText(organization?.taxIds); if (hasTaxIdsConfig) { if (printedTaxIdsText) { stack.push({ text: printedTaxIdsText, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } } else { if (organization?.gstin) { stack.push({ text: `GSTIN: ${organization.gstin}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.gst) { stack.push({ text: `GST: ${organization.gst}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.cst) { stack.push({ text: `CST: ${organization.cst}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.cin) { stack.push({ text: `CIN: ${organization.cin}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.pan) { stack.push({ text: `PAN: ${organization.pan}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } } if (organization?.dlNo) { stack.push({ text: `DLNo: ${organization.dlNo}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (organization?.email) { stack.push({ text: `Email: ${organization.email}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } const phoneNumbers = [organization?.phone, organization?.phone2] .filter((phone: string) => !!phone) .join(', '); if (phoneNumbers) { stack.push({ text: `Phone: ${phoneNumbers}`, fontSize: orgAddressFontSize, color: orgAddressFontColor, lineHeight: 1.05, alignment }); } if (stack.length === 0) { stack.push({ text: '' }); } return stack; } function normalizeTaxIds(value: any) { if (!Array.isArray(value)) { return []; } return value.map((taxId: any) => ({ Label: toStr(taxId?.Label ?? taxId?.label), Value: toStr(taxId?.Value ?? taxId?.value), Print: normalizeBooleanLike(taxId?.Print ?? taxId?.print), Primary: normalizeBooleanLike(taxId?.Primary ?? taxId?.primary) })); } function buildPrintedTaxIdsText(value: any) { const taxIds = normalizeTaxIds(value); const primary: string[] = []; const secondary: string[] = []; for (const taxId of taxIds) { if (!taxId?.Print || !taxId?.Value) { continue; } const text = taxId?.Label ? `${taxId.Label}:${taxId.Value}` : taxId.Value; if (taxId?.Primary) { primary.push(text); } else { secondary.push(text); } } return [...primary, ...secondary].join(', '); } function normalizeBooleanLike(value: any) { if (typeof value === 'boolean') { return value; } if (typeof value === 'number') { return value !== 0; } if (typeof value === 'string') { const normalized = value.trim().toLowerCase(); return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'y'; } return false; } function normalizeHeaderSettings(value: any) { const Position = normalizePosition(value?.Position, DEFAULT_HEADER_POSITION); const LogoPosition = normalizeLogoPosition(value?.LogoPosition, DEFAULT_LOGO_POSITION); return { Position, LogoPosition, Show: value?.Show !== false, ShowLogo: value?.ShowLogo !== false }; } function normalizePosition(value: any, fallback: string): 'Left' | 'Center' | 'Right' { const raw = String(value ?? '').trim().toLowerCase(); if (raw === 'center') { return 'Center'; } if (raw === 'right') { return 'Right'; } if (raw === 'left') { return 'Left'; } return fallback as 'Left' | 'Center' | 'Right'; } function normalizeLogoPosition(value: any, fallback: string): 'Left' | 'Top' | 'Right' { const raw = String(value ?? '').trim().toLowerCase(); if (raw === 'top') { return 'Top'; } if (raw === 'right') { return 'Right'; } if (raw === 'left') { return 'Left'; } return fallback as 'Left' | 'Top' | 'Right'; } function toPdfAlignment(position: 'Left' | 'Center' | 'Right'): 'left' | 'center' | 'right' { if (position === 'Center') { return 'center'; } if (position === 'Right') { return 'right'; } return 'left'; } function buildHeaderContentWithLogo( logoDataUrl: string | null, hasLogo: boolean, organizationStack: any[], logoPosition: 'Left' | 'Top' | 'Right', textAlignment: 'left' | 'center' | 'right' ) { const safeOrganizationStack = Array.isArray(organizationStack) ? organizationStack : [{ text: toStr(organizationStack) }]; if (!hasLogo || !logoDataUrl) { return { stack: safeOrganizationStack }; } const logoBlock = { width: 120, image: logoDataUrl, // fit: [80, 80], height:80, margin: [0, -15, 6, 0] }; if (logoPosition === 'Top') { return { stack: [ { image: logoDataUrl, fit: [72, 72], alignment: textAlignment, margin: [0, 2, 0, 6] }, ...safeOrganizationStack ] }; } if (logoPosition === 'Right') { return { columns: [ { width: '*', stack: safeOrganizationStack }, { ...logoBlock, margin: [6, 2, 0, 0] } ], columnGap: 8 }; } return { columns: [ logoBlock, { width: '*', marginLeft: textAlignment === 'center' ? -110 : 0, stack: safeOrganizationStack } ], columnGap: 8 }; }