535 lines
17 KiB
TypeScript
535 lines
17 KiB
TypeScript
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|||
|
import * as plugins from '../plugins.js';
|
|||
|
import { EInvoice } from '../../../ts/index.js';
|
|||
|
import { CorpusLoader } from '../corpus.loader.js';
|
|||
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|||
|
|
|||
|
tap.test('PDF-11: PDF/A Compliance - should ensure PDF/A standard compliance', async (t) => {
|
|||
|
// PDF-11: Verify PDF/A compliance for long-term archiving
|
|||
|
// This test ensures PDFs meet PDF/A standards for electronic invoicing
|
|||
|
|
|||
|
const performanceTracker = new PerformanceTracker('PDF-11: PDF/A Compliance');
|
|||
|
const corpusLoader = new CorpusLoader();
|
|||
|
|
|||
|
t.test('Create PDF/A-3 compliant document', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument, PDFName } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
// PDF/A-3 allows embedded files (required for ZUGFeRD/Factur-X)
|
|||
|
// Set PDF/A identification
|
|||
|
pdfDoc.setTitle('PDF/A-3 Compliant Invoice');
|
|||
|
pdfDoc.setAuthor('EInvoice System');
|
|||
|
pdfDoc.setSubject('Electronic Invoice with embedded XML');
|
|||
|
pdfDoc.setKeywords(['PDF/A-3', 'ZUGFeRD', 'Factur-X', 'invoice']);
|
|||
|
pdfDoc.setCreator('EInvoice PDF/A Generator');
|
|||
|
pdfDoc.setProducer('PDFLib with PDF/A-3 compliance');
|
|||
|
|
|||
|
// Add required metadata for PDF/A
|
|||
|
const creationDate = new Date('2025-01-25T10:00:00Z');
|
|||
|
const modDate = new Date('2025-01-25T10:00:00Z');
|
|||
|
pdfDoc.setCreationDate(creationDate);
|
|||
|
pdfDoc.setModificationDate(modDate);
|
|||
|
|
|||
|
// Create page with required elements for PDF/A
|
|||
|
const page = pdfDoc.addPage([595, 842]); // A4
|
|||
|
|
|||
|
// Use embedded fonts (required for PDF/A)
|
|||
|
const helveticaFont = await pdfDoc.embedFont('Helvetica');
|
|||
|
|
|||
|
// Add content
|
|||
|
page.drawText('PDF/A-3 Compliant Invoice', {
|
|||
|
x: 50,
|
|||
|
y: 750,
|
|||
|
size: 20,
|
|||
|
font: helveticaFont
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Invoice Number: INV-2025-001', {
|
|||
|
x: 50,
|
|||
|
y: 700,
|
|||
|
size: 12,
|
|||
|
font: helveticaFont
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('This document complies with PDF/A-3 standard', {
|
|||
|
x: 50,
|
|||
|
y: 650,
|
|||
|
size: 10,
|
|||
|
font: helveticaFont
|
|||
|
});
|
|||
|
|
|||
|
// Add required OutputIntent for PDF/A
|
|||
|
// Note: pdf-lib doesn't directly support OutputIntent
|
|||
|
// In production, a specialized library would be needed
|
|||
|
|
|||
|
// Embed invoice XML (allowed in PDF/A-3)
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
|||
|
<rsm:ExchangedDocument>
|
|||
|
<ram:ID xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">INV-2025-001</ram:ID>
|
|||
|
<ram:TypeCode xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">380</ram:TypeCode>
|
|||
|
<ram:IssueDateTime xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">
|
|||
|
<udt:DateTimeString xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" format="102">20250125</udt:DateTimeString>
|
|||
|
</ram:IssueDateTime>
|
|||
|
</rsm:ExchangedDocument>
|
|||
|
</rsm:CrossIndustryInvoice>`;
|
|||
|
|
|||
|
await pdfDoc.attach(
|
|||
|
Buffer.from(xmlContent, 'utf8'),
|
|||
|
'invoice.xml',
|
|||
|
{
|
|||
|
mimeType: 'application/xml',
|
|||
|
description: 'ZUGFeRD invoice data',
|
|||
|
afRelationship: plugins.AFRelationship.Data,
|
|||
|
creationDate: creationDate,
|
|||
|
modificationDate: modDate
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
const pdfBytes = await pdfDoc.save();
|
|||
|
|
|||
|
// Verify basic structure
|
|||
|
expect(pdfBytes.length).toBeGreaterThan(0);
|
|||
|
console.log('Created PDF/A-3 structure (full compliance requires specialized tools)');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('pdfa3-creation', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('PDF/A-1b compliance check', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
// PDF/A-1b: Basic compliance (visual appearance preservation)
|
|||
|
pdfDoc.setTitle('PDF/A-1b Test Document');
|
|||
|
pdfDoc.setCreationDate(new Date());
|
|||
|
|
|||
|
const page = pdfDoc.addPage();
|
|||
|
|
|||
|
// PDF/A-1b requirements:
|
|||
|
// - All fonts must be embedded
|
|||
|
// - No transparency
|
|||
|
// - No JavaScript
|
|||
|
// - No audio/video
|
|||
|
// - No encryption
|
|||
|
// - Proper color space definition
|
|||
|
|
|||
|
const helveticaFont = await pdfDoc.embedFont('Helvetica');
|
|||
|
|
|||
|
page.drawText('PDF/A-1b Compliant Document', {
|
|||
|
x: 50,
|
|||
|
y: 750,
|
|||
|
size: 16,
|
|||
|
font: helveticaFont,
|
|||
|
color: { red: 0, green: 0, blue: 0 } // RGB color space
|
|||
|
});
|
|||
|
|
|||
|
// Add text without transparency
|
|||
|
page.drawText('No transparency allowed in PDF/A-1b', {
|
|||
|
x: 50,
|
|||
|
y: 700,
|
|||
|
size: 12,
|
|||
|
font: helveticaFont,
|
|||
|
color: { red: 0, green: 0, blue: 0 },
|
|||
|
opacity: 1.0 // Full opacity required
|
|||
|
});
|
|||
|
|
|||
|
// Draw rectangle without transparency
|
|||
|
page.drawRectangle({
|
|||
|
x: 50,
|
|||
|
y: 600,
|
|||
|
width: 200,
|
|||
|
height: 50,
|
|||
|
color: { red: 0.9, green: 0.9, blue: 0.9 },
|
|||
|
borderColor: { red: 0, green: 0, blue: 0 },
|
|||
|
borderWidth: 1,
|
|||
|
opacity: 1.0
|
|||
|
});
|
|||
|
|
|||
|
const pdfBytes = await pdfDoc.save();
|
|||
|
|
|||
|
// Check for PDF/A-1b violations
|
|||
|
const pdfString = pdfBytes.toString('binary');
|
|||
|
|
|||
|
// Check for prohibited features
|
|||
|
const violations = [];
|
|||
|
if (pdfString.includes('/JS')) violations.push('JavaScript detected');
|
|||
|
if (pdfString.includes('/Launch')) violations.push('External launch action detected');
|
|||
|
if (pdfString.includes('/Sound')) violations.push('Sound annotation detected');
|
|||
|
if (pdfString.includes('/Movie')) violations.push('Movie annotation detected');
|
|||
|
if (pdfString.includes('/Encrypt')) violations.push('Encryption detected');
|
|||
|
|
|||
|
console.log('PDF/A-1b compliance check:');
|
|||
|
if (violations.length === 0) {
|
|||
|
console.log('No obvious violations detected');
|
|||
|
} else {
|
|||
|
console.log('Potential violations:', violations);
|
|||
|
}
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('pdfa1b-compliance', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('PDF/A metadata requirements', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
// Required XMP metadata for PDF/A
|
|||
|
const xmpMetadata = `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|||
|
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
|||
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|||
|
<rdf:Description rdf:about=""
|
|||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|||
|
xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/"
|
|||
|
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
|||
|
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
|
|||
|
<dc:title>
|
|||
|
<rdf:Alt>
|
|||
|
<rdf:li xml:lang="x-default">PDF/A Compliant Invoice</rdf:li>
|
|||
|
</rdf:Alt>
|
|||
|
</dc:title>
|
|||
|
<dc:creator>
|
|||
|
<rdf:Seq>
|
|||
|
<rdf:li>EInvoice System</rdf:li>
|
|||
|
</rdf:Seq>
|
|||
|
</dc:creator>
|
|||
|
<dc:description>
|
|||
|
<rdf:Alt>
|
|||
|
<rdf:li xml:lang="x-default">Invoice with PDF/A compliance</rdf:li>
|
|||
|
</rdf:Alt>
|
|||
|
</dc:description>
|
|||
|
<pdfaid:part>3</pdfaid:part>
|
|||
|
<pdfaid:conformance>B</pdfaid:conformance>
|
|||
|
<xmp:CreateDate>2025-01-25T10:00:00Z</xmp:CreateDate>
|
|||
|
<xmp:ModifyDate>2025-01-25T10:00:00Z</xmp:ModifyDate>
|
|||
|
<xmp:MetadataDate>2025-01-25T10:00:00Z</xmp:MetadataDate>
|
|||
|
<pdf:Producer>EInvoice PDF/A Generator</pdf:Producer>
|
|||
|
</rdf:Description>
|
|||
|
</rdf:RDF>
|
|||
|
</x:xmpmeta>
|
|||
|
<?xpacket end="w"?>`;
|
|||
|
|
|||
|
// Set standard metadata
|
|||
|
pdfDoc.setTitle('PDF/A Compliant Invoice');
|
|||
|
pdfDoc.setAuthor('EInvoice System');
|
|||
|
pdfDoc.setSubject('Invoice with PDF/A compliance');
|
|||
|
pdfDoc.setKeywords(['PDF/A', 'invoice', 'compliant']);
|
|||
|
|
|||
|
const page = pdfDoc.addPage();
|
|||
|
page.drawText('Document with PDF/A Metadata', { x: 50, y: 750, size: 16 });
|
|||
|
|
|||
|
// Note: pdf-lib doesn't support direct XMP metadata embedding
|
|||
|
// This would require post-processing or a specialized library
|
|||
|
|
|||
|
console.log('PDF/A metadata structure defined (requires specialized tools for embedding)');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('pdfa-metadata', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Color space compliance', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
const page = pdfDoc.addPage();
|
|||
|
|
|||
|
// PDF/A requires proper color space definitions
|
|||
|
// Test different color spaces
|
|||
|
|
|||
|
// Device RGB (most common for screen display)
|
|||
|
page.drawText('Device RGB Color Space', {
|
|||
|
x: 50,
|
|||
|
y: 750,
|
|||
|
size: 14,
|
|||
|
color: { red: 0.8, green: 0.2, blue: 0.2 }
|
|||
|
});
|
|||
|
|
|||
|
// Grayscale
|
|||
|
page.drawText('Device Gray Color Space', {
|
|||
|
x: 50,
|
|||
|
y: 700,
|
|||
|
size: 14,
|
|||
|
color: { red: 0.5, green: 0.5, blue: 0.5 }
|
|||
|
});
|
|||
|
|
|||
|
// Test color accuracy
|
|||
|
const colors = [
|
|||
|
{ name: 'Pure Red', rgb: { red: 1, green: 0, blue: 0 } },
|
|||
|
{ name: 'Pure Green', rgb: { red: 0, green: 1, blue: 0 } },
|
|||
|
{ name: 'Pure Blue', rgb: { red: 0, green: 0, blue: 1 } },
|
|||
|
{ name: 'Black', rgb: { red: 0, green: 0, blue: 0 } },
|
|||
|
{ name: 'White', rgb: { red: 1, green: 1, blue: 1 } }
|
|||
|
];
|
|||
|
|
|||
|
let yPos = 600;
|
|||
|
colors.forEach(color => {
|
|||
|
page.drawRectangle({
|
|||
|
x: 50,
|
|||
|
y: yPos,
|
|||
|
width: 30,
|
|||
|
height: 20,
|
|||
|
color: color.rgb
|
|||
|
});
|
|||
|
|
|||
|
page.drawText(color.name, {
|
|||
|
x: 90,
|
|||
|
y: yPos + 5,
|
|||
|
size: 10,
|
|||
|
color: { red: 0, green: 0, blue: 0 }
|
|||
|
});
|
|||
|
|
|||
|
yPos -= 30;
|
|||
|
});
|
|||
|
|
|||
|
// Add OutputIntent description
|
|||
|
page.drawText('OutputIntent: sRGB IEC61966-2.1', {
|
|||
|
x: 50,
|
|||
|
y: 400,
|
|||
|
size: 10,
|
|||
|
color: { red: 0, green: 0, blue: 0 }
|
|||
|
});
|
|||
|
|
|||
|
const pdfBytes = await pdfDoc.save();
|
|||
|
console.log('Created PDF with color space definitions for PDF/A');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('color-space', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Font embedding compliance', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
// PDF/A requires all fonts to be embedded
|
|||
|
const page = pdfDoc.addPage();
|
|||
|
|
|||
|
// Embed standard fonts
|
|||
|
const helvetica = await pdfDoc.embedFont('Helvetica');
|
|||
|
const helveticaBold = await pdfDoc.embedFont('Helvetica-Bold');
|
|||
|
const helveticaOblique = await pdfDoc.embedFont('Helvetica-Oblique');
|
|||
|
const timesRoman = await pdfDoc.embedFont('Times-Roman');
|
|||
|
const courier = await pdfDoc.embedFont('Courier');
|
|||
|
|
|||
|
// Use embedded fonts
|
|||
|
page.drawText('Helvetica Regular (Embedded)', {
|
|||
|
x: 50,
|
|||
|
y: 750,
|
|||
|
size: 14,
|
|||
|
font: helvetica
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Helvetica Bold (Embedded)', {
|
|||
|
x: 50,
|
|||
|
y: 720,
|
|||
|
size: 14,
|
|||
|
font: helveticaBold
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Helvetica Oblique (Embedded)', {
|
|||
|
x: 50,
|
|||
|
y: 690,
|
|||
|
size: 14,
|
|||
|
font: helveticaOblique
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Times Roman (Embedded)', {
|
|||
|
x: 50,
|
|||
|
y: 660,
|
|||
|
size: 14,
|
|||
|
font: timesRoman
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Courier (Embedded)', {
|
|||
|
x: 50,
|
|||
|
y: 630,
|
|||
|
size: 14,
|
|||
|
font: courier
|
|||
|
});
|
|||
|
|
|||
|
// Test font subset embedding
|
|||
|
page.drawText('Font Subset Test: €£¥§¶•', {
|
|||
|
x: 50,
|
|||
|
y: 580,
|
|||
|
size: 14,
|
|||
|
font: helvetica
|
|||
|
});
|
|||
|
|
|||
|
const pdfBytes = await pdfDoc.save();
|
|||
|
|
|||
|
// Check font embedding
|
|||
|
const pdfString = pdfBytes.toString('binary');
|
|||
|
const fontCount = (pdfString.match(/\/Type\s*\/Font/g) || []).length;
|
|||
|
console.log(`Embedded fonts count: ${fontCount}`);
|
|||
|
|
|||
|
expect(fontCount).toBeGreaterThan(0);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('font-embedding', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('PDF/A-3 with ZUGFeRD attachment', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const { PDFDocument, AFRelationship } = plugins;
|
|||
|
const pdfDoc = await PDFDocument.create();
|
|||
|
|
|||
|
// Configure for ZUGFeRD/Factur-X compliance
|
|||
|
pdfDoc.setTitle('ZUGFeRD Invoice PDF/A-3');
|
|||
|
pdfDoc.setAuthor('ZUGFeRD Generator');
|
|||
|
pdfDoc.setSubject('Electronic Invoice with embedded XML');
|
|||
|
pdfDoc.setKeywords(['ZUGFeRD', 'PDF/A-3', 'Factur-X', 'electronic invoice']);
|
|||
|
pdfDoc.setCreator('EInvoice ZUGFeRD Module');
|
|||
|
|
|||
|
const page = pdfDoc.addPage();
|
|||
|
const helvetica = await pdfDoc.embedFont('Helvetica');
|
|||
|
|
|||
|
// Invoice header
|
|||
|
page.drawText('RECHNUNG / INVOICE', {
|
|||
|
x: 50,
|
|||
|
y: 750,
|
|||
|
size: 20,
|
|||
|
font: helvetica
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Rechnungsnummer / Invoice No: 2025-001', {
|
|||
|
x: 50,
|
|||
|
y: 700,
|
|||
|
size: 12,
|
|||
|
font: helvetica
|
|||
|
});
|
|||
|
|
|||
|
page.drawText('Rechnungsdatum / Invoice Date: 25.01.2025', {
|
|||
|
x: 50,
|
|||
|
y: 680,
|
|||
|
size: 12,
|
|||
|
font: helvetica
|
|||
|
});
|
|||
|
|
|||
|
// ZUGFeRD XML attachment
|
|||
|
const zugferdXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|||
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|||
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
|||
|
<rsm:ExchangedDocumentContext>
|
|||
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
|||
|
<ram:ID>urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p1:extended</ram:ID>
|
|||
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
|||
|
</rsm:ExchangedDocumentContext>
|
|||
|
<rsm:ExchangedDocument>
|
|||
|
<ram:ID>2025-001</ram:ID>
|
|||
|
<ram:TypeCode>380</ram:TypeCode>
|
|||
|
<ram:IssueDateTime>
|
|||
|
<udt:DateTimeString format="102">20250125</udt:DateTimeString>
|
|||
|
</ram:IssueDateTime>
|
|||
|
</rsm:ExchangedDocument>
|
|||
|
</rsm:CrossIndustryInvoice>`;
|
|||
|
|
|||
|
// Attach with proper relationship for ZUGFeRD
|
|||
|
await pdfDoc.attach(
|
|||
|
Buffer.from(zugferdXml, 'utf8'),
|
|||
|
'zugferd-invoice.xml',
|
|||
|
{
|
|||
|
mimeType: 'application/xml',
|
|||
|
description: 'ZUGFeRD Invoice Data',
|
|||
|
afRelationship: AFRelationship.Data
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
const pdfBytes = await pdfDoc.save();
|
|||
|
|
|||
|
// Test loading
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromPdfBuffer(pdfBytes);
|
|||
|
|
|||
|
console.log('Created PDF/A-3 compliant ZUGFeRD invoice');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('zugferd-pdfa3', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Corpus PDF/A compliance check', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
let pdfaCount = 0;
|
|||
|
let processedCount = 0;
|
|||
|
const complianceIndicators = {
|
|||
|
'PDF/A identification': 0,
|
|||
|
'Embedded fonts': 0,
|
|||
|
'No encryption': 0,
|
|||
|
'Metadata present': 0,
|
|||
|
'Color space defined': 0
|
|||
|
};
|
|||
|
|
|||
|
const files = await corpusLoader.getAllFiles();
|
|||
|
const pdfFiles = files.filter(f => f.endsWith('.pdf'));
|
|||
|
|
|||
|
// Sample PDFs for PDF/A compliance indicators
|
|||
|
const sampleSize = Math.min(40, pdfFiles.length);
|
|||
|
const sample = pdfFiles.slice(0, sampleSize);
|
|||
|
|
|||
|
for (const file of sample) {
|
|||
|
try {
|
|||
|
const content = await corpusLoader.readFile(file);
|
|||
|
const pdfString = content.toString('binary');
|
|||
|
|
|||
|
// Check for PDF/A indicators
|
|||
|
let isPdfA = false;
|
|||
|
|
|||
|
if (pdfString.includes('pdfaid:part') || pdfString.includes('PDF/A')) {
|
|||
|
isPdfA = true;
|
|||
|
complianceIndicators['PDF/A identification']++;
|
|||
|
}
|
|||
|
|
|||
|
if (pdfString.includes('/Type /Font') && pdfString.includes('/FontFile')) {
|
|||
|
complianceIndicators['Embedded fonts']++;
|
|||
|
}
|
|||
|
|
|||
|
if (!pdfString.includes('/Encrypt')) {
|
|||
|
complianceIndicators['No encryption']++;
|
|||
|
}
|
|||
|
|
|||
|
if (pdfString.includes('/Metadata') || pdfString.includes('xmpmeta')) {
|
|||
|
complianceIndicators['Metadata present']++;
|
|||
|
}
|
|||
|
|
|||
|
if (pdfString.includes('/OutputIntent') || pdfString.includes('/ColorSpace')) {
|
|||
|
complianceIndicators['Color space defined']++;
|
|||
|
}
|
|||
|
|
|||
|
if (isPdfA) {
|
|||
|
pdfaCount++;
|
|||
|
console.log(`Potential PDF/A file: ${file}`);
|
|||
|
}
|
|||
|
|
|||
|
processedCount++;
|
|||
|
} catch (error) {
|
|||
|
console.log(`Error checking ${file}:`, error.message);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
console.log(`Corpus PDF/A analysis (${processedCount} PDFs):`);
|
|||
|
console.log(`- Potential PDF/A files: ${pdfaCount}`);
|
|||
|
console.log('Compliance indicators:', complianceIndicators);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('corpus-pdfa', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
// Print performance summary
|
|||
|
performanceTracker.printSummary();
|
|||
|
|
|||
|
// Performance assertions
|
|||
|
const avgTime = performanceTracker.getAverageTime();
|
|||
|
expect(avgTime).toBeLessThan(400); // PDF/A operations may take longer
|
|||
|
});
|
|||
|
|
|||
|
tap.start();
|