La complexité cachée de la structure PDF.
Les documents PDF sont beaucoup plus sophistiqués qu'ils ne le paraissent aux utilisateurs finaux. Bien que les utilisateurs voient les pages dans un ordre logique et séquentiel (1, 2, 3...), l'architecture interne d'un fichier PDF raconte une histoire très différente. Cette complexité est l'un des aspects les plus mal compris du traitement des PDF, ce qui entraîne d'innombrables bogues, des implémentations incorrectes et des développeurs frustrés. Cet article complet explore le monde complexe de l'organisation des pages PDF, explique pourquoi les développeurs rencontrent fréquemment des problèmes inattendus d'ordre des pages et propose des solutions pratiques pour la manipulation robuste des PDF.
Le modèle d'objet PDF : un changement de paradigme par rapport aux documents séquentiels.
Pour comprendre les défis liés à l'ordre des pages PDF, nous devons d'abord comprendre à quel point le PDF est différent des formats de documents plus simples. Contrairement aux fichiers texte brut, aux documents HTML ou même aux formats plus anciens comme RTF, le PDF utilise une architecture sophistiquée basée sur des objets, où l'organisation du contenu et le stockage physique sont complètement découplés.
Cette décision architecturale a été prise pour plusieurs raisons importantes :
- Flexibilité: Les objets peuvent être référencés à partir de plusieurs emplacements sans duplication.
- Efficacité : Les ressources communes (polices, images, états graphiques) peuvent être partagées entre les pages.
- Mises à jour incrémentielles : Les documents peuvent être modifiés sans réécrire l'intégralité du fichier.
- Accès aléatoire : Les utilisateurs peuvent accéder à n'importe quelle page sans analyser l'intégralité du document.
Cependant, cette flexibilité a un coût en termes de complexité, notamment en ce qui concerne la compréhension de la relation entre l'ordre de stockage des objets et la séquence logique des pages.
Références d'objets vs. Ordre d'affichage : Un exemple concret.
Considérons cette structure PDF typique qui illustre le décalage entre le stockage et l'affichage :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
% PDF file structure example - storage order vs. display order %PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [20 0 R 1 0 R 4 0 R] /Count 3 >> endobj % Object 4 appears third in file but represents page 3 in display 4 0 obj << /Type /Page /Contents 5 0 R /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 6 0 R >> >> >> endobj % Object 20 appears last in file but represents page 1 in display 20 0 obj << /Type /Page /Contents 21 0 R /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 6 0 R >> >> >> endobj |
Dans cet exemple, les objets de page sont stockés comme les objets 4 et 20, mais l'ordre d'affichage est défini par le tableau Kids : [20, 1, 4]. Cela crée la correspondance suivante :
- Page 1 (ordre d'affichage) = Objet 20 (ordre de stockage : dernier).
- Page 2 (ordre d'affichage) = Objet 1 (ordre de stockage : premier).
- Page 3 (ordre d'affichage) = Objet 4 (ordre de stockage : troisième).
Cette incohérence n'est pas accidentelle ; c'est une caractéristique fondamentale du PDF qui permet une manipulation et une optimisation sophistiquées des documents.
Pourquoi les générateurs PDF créent-ils des ordres d'objets non séquentiels.
Comprendre pourquoi les générateurs PDF créent des ordres d'objets non séquentiels aide les développeurs à apprécier la complexité avec laquelle ils travaillent et à éviter de faire des hypothèses incorrectes sur la structure du document.
Flux de travail de création de PDF.
Différents flux de travail de création de PDF entraînent des schémas d'ordonnancement d'objets différents :
1. Création de document séquentielle.
|
1 2 3 4 5 6 |
% Typical output from simple PDF generators 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >> endobj 3 0 obj << /Type /Page /Contents 6 0 R /Parent 2 0 R >> endobj 4 0 obj << /Type /Page /Contents 7 0 R /Parent 2 0 R >> endobj 5 0 obj << /Type /Page /Contents 8 0 R /Parent 2 0 R >> endobj |
2. Partage optimisé des ressources.
|
1 2 3 4 5 6 7 8 9 |
% PDF with shared resources created first 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [10 0 R 11 0 R 12 0 R] /Count 3 >> endobj 3 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 4 0 obj << /Type /XObject /Subtype /Image /Width 100 /Height 100 >> endobj % ... more shared resources ... 10 0 obj << /Type /Page /Resources << /Font << /F1 3 0 R >> >> >> endobj 11 0 obj << /Type /Page /Resources << /XObject << /Im1 4 0 R >> >> >> endobj 12 0 obj << /Type /Page /Resources << /Font << /F1 3 0 R >> >> >> endobj |
3. Assemblage incrémental de documents.
|
1 2 3 4 5 6 7 8 9 |
% Document created by combining existing PDFs 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [100 0 R 25 0 R 75 0 R] /Count 3 >> endobj % Objects from first source document 25 0 obj << /Type /Page /Contents 26 0 R /Parent 2 0 R >> endobj % Objects from second source document 75 0 obj << /Type /Page /Contents 76 0 R /Parent 2 0 R >> endobj % Objects from third source document 100 0 obj << /Type /Page /Contents 101 0 R /Parent 2 0 R >> endobj |
Erreurs courantes des développeurs et leurs conséquences.
La complexité de la structure PDF conduit à plusieurs erreurs courantes qui peuvent avoir de graves conséquences sur la fiabilité de l'application et l'expérience utilisateur.
Erreur 1 : Supposer que l'ordre des ID d'objet correspond à l'ordre d'affichage.
Il s'agit peut-être de l'erreur la plus courante commise par les développeurs débutants en traitement PDF :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// WRONG: Processing pages by object ID order function GetPagesInWrongOrder(Doc: TPDFDocument): TPageList; var i: Integer; Obj: TPDFObject; begin Result := TPageList.Create; // This approach processes pages in storage order, not display order for i := 0 to Doc.Objects.Count - 1 do begin Obj := Doc.Objects[i]; if (Obj <> nil) and (Obj.GetValue('/Type') = '/Page') then begin Result.Add(Obj); // Wrong order! end; end; // Result will be in object ID order: [1, 4, 20] // But display order should be: [20, 1, 4] end; |
Les conséquences de cette erreur comprennent :
- Les pages apparaissent dans un ordre incorrect dans les documents de sortie.
- La numérotation des pages devient incohérente.
- Confusion des utilisateurs et demandes de support.
- Risque de corruption des données dans les pipelines de traitement des documents.
Erreur 2 : Correspondance de pages codée en dur basée sur des observations.
Lorsque les développeurs rencontrent des problèmes d'ordre des pages, ils mettent parfois en œuvre des corrections codées en dur basées sur des schémas observés :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// WRONG: Hard-coded page reordering based on heuristics function ApplyPageReorderingHeuristics(Pages: TPageArray): TPageArray; var i: Integer; begin SetLength(Result, Length(Pages)); // Dangerous heuristic based on limited observations if Length(Pages) = 3 then begin // "Fix" for specific 3-page documents observed during testing Result[0] := Pages[1]; // Put second page first Result[1] := Pages[2]; // Put third page second Result[2] := Pages[0]; // Put first page last end else if Length(Pages) > 3 then begin // Generic "fix" that swaps first and last pages Result[0] := Pages[Length(Pages) - 1]; Result[Length(Pages) - 1] := Pages[0]; // Keep middle pages in original order for i := 1 to Length(Pages) - 2 do Result[i] := Pages[i]; end else begin // For other cases, just copy as-is for i := 0 to High(Pages) do Result[i] := Pages[i]; end; end; |
Cette approche est fondamentalement défectueuse car :
- Elle ne fonctionne que pour les fichiers PDF spécifiques observés pendant le développement.
- Elle échoue de manière catastrophique avec les fichiers PDF qui ont des structures différentes.
- Elle crée un comportement imprévisible que les utilisateurs ne peuvent pas comprendre.
- Elle accumule une dette technique à mesure que de nouveaux cas particuliers sont ajoutés.
Erreur 3 : Ignorer les arbres de pages hiérarchiques.
De nombreux développeurs supposent que les arbres de pages PDF sont toujours des tableaux plats, mais la spécification PDF autorise des structures hiérarchiques :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// WRONG: Assuming flat page tree structure function GetPagesFromFlatTree(PagesObj: TPDFObject): TPageArray; var KidsArray: TPDFArray; i: Integer; begin KidsArray := PagesObj.GetArray('/Kids'); if KidsArray = nil then Exit; SetLength(Result, KidsArray.Count); for i := 0 to KidsArray.Count - 1 do begin // This assumes all Kids entries are Page objects // But they might be intermediate Pages objects! Result[i] := KidsArray.GetIndirectObject(i); end; end; |
L'approche correcte : Suivre la structure de l'arbre de pages.
La bonne façon de gérer l'ordre des pages PDF est de mettre en œuvre un parcours complet de l'arbre de pages qui suit exactement la spécification PDF.
Comprendre la hiérarchie de l'arbre de pages.
Les arbres de pages PDF peuvent être hiérarchiques, les objets Pages intermédiaires contenant leurs propres tableaux "Kids" :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
% Hierarchical page tree example 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj % Root Pages object 2 0 obj << /Type /Pages /Kids [3 0 R 8 0 R 15 0 R] /Count 7 >> endobj % First intermediate Pages object (contains 3 pages) 3 0 obj << /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 /Parent 2 0 R >> endobj % Second intermediate Pages object (contains 2 pages) 8 0 obj << /Type /Pages /Kids [9 0 R 10 0 R] /Count 2 /Parent 2 0 R >> endobj % Third intermediate Pages object (contains 2 pages) 15 0 obj << /Type /Pages /Kids [16 0 R 17 0 R] /Count 2 /Parent 2 0 R >> endobj % Actual page objects 4 0 obj << /Type /Page /Contents 40 0 R /Parent 3 0 R >> endobj 5 0 obj << /Type /Page /Contents 41 0 R /Parent 3 0 R >> endobj % ... and so on |
Implémenter un parcours récursif de l'arbre de pages.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// CORRECT: Recursive page tree traversal function GetPagesInCorrectOrder(Doc: TPDFDocument): TPageArray; var CatalogObj, RootPagesObj: TPDFObject; PageList: TList; begin PageList := TList.Create; try // Step 1: Find the document catalog CatalogObj := Doc.FindObject('/Type', '/Catalog'); if CatalogObj = nil then raise Exception.Create('Document catalog not found'); // Step 2: Get the root Pages object RootPagesObj := CatalogObj.GetIndirectObject('/Pages'); if RootPagesObj = nil then raise Exception.Create('Root Pages object not found'); // Step 3: Recursively traverse the page tree TraversePagesTree(RootPagesObj, PageList); // Step 4: Convert list to array SetLength(Result, PageList.Count); for i := 0 to PageList.Count - 1 do Result[i] := TPDFObject(PageList[i]); finally PageList.Free; end; end; procedure TraversePagesTree(PagesObj: TPDFObject; PageList: TList); var KidsArray: TPDFArray; i: Integer; ChildObj: TPDFObject; ChildType: string; begin if PagesObj = nil then Exit; // Get the Kids array from this Pages object KidsArray := PagesObj.GetArray('/Kids'); if KidsArray = nil then Exit; // Process each child in the Kids array for i := 0 to KidsArray.Count - 1 do begin ChildObj := KidsArray.GetIndirectObject(i); if ChildObj = nil then Continue; ChildType := ChildObj.GetValue('/Type'); if ChildType = '/Page' then begin // This is a leaf page object - add it to our list PageList.Add(ChildObj); end else if ChildType = '/Pages' then begin // This is an intermediate Pages object - recurse into it TraversePagesTree(ChildObj, PageList); end else begin // Unexpected object type in Kids array raise Exception.CreateFmt('Unexpected object type in Kids array: %s', [ChildType]); end; end; end; |
Gestion des variations et des cas limites réels des fichiers PDF.
Les fichiers PDF réels s'écartent souvent de la structure idéale décrite dans la spécification. Une bibliothèque de traitement PDF robuste doit gérer ces variations avec élégance.
Anomalies structurelles courantes.
1. Catalogue manquant ou corrompu.
|
1 2 3 4 5 6 |
% PDF with missing catalog reference %PDF-1.4 % Object 1 should be catalog but is missing or corrupted 2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >> endobj |
2. Références circulaires.
|
1 2 3 4 5 6 7 8 |
% PDF with circular page tree references (corrupted) 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 /Parent 3 0 R >> endobj 3 0 obj << /Type /Pages /Kids [2 0 R] /Count 1 /Parent 2 0 R >> endobj |
3. Valeurs de comptage incohérentes.
|
1 2 3 4 5 |
% PDF with incorrect Count value 2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 5 >> % Count says 5 but Kids array has only 3 elements endobj |
Mise en œuvre d'une gestion robuste des erreurs.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
// Robust page tree traversal with comprehensive error handling function GetPagesWithFallbacks(Doc: TPDFDocument): TPageArray; var AttemptCount: Integer; ErrorMessages: TStringList; begin ErrorMessages := TStringList.Create; try AttemptCount := 0; // Attempt 1: Standard PDF specification approach Inc(AttemptCount); try Result := GetPagesViaStandardTraversal(Doc); if Length(Result) > 0 then begin LogMessage(Format('Success with standard traversal (attempt %d)', [AttemptCount])); Exit; end; except on E: Exception do ErrorMessages.Add(Format('Attempt %d failed: %s', [AttemptCount, E.Message])); end; // Attempt 2: Search for Pages objects and try each one Inc(AttemptCount); try Result := GetPagesViaObjectSearch(Doc); if Length(Result) > 0 then begin LogMessage(Format('Success with object search (attempt %d)', [AttemptCount])); Exit; end; except on E: Exception do ErrorMessages.Add(Format('Attempt %d failed: %s', [AttemptCount, E.Message])); end; // Attempt 3: Brute force search for Page objects Inc(AttemptCount); try Result := GetPagesViaBruteForce(Doc); if Length(Result) > 0 then begin LogMessage(Format('Success with brute force search (attempt %d)', [AttemptCount])); LogMessage('Warning: Document structure is non-standard'); Exit; end; except on E: Exception do ErrorMessages.Add(Format('Attempt %d failed: %s', [AttemptCount, E.Message])); end; // All attempts failed raise Exception.Create('Failed to extract pages from PDF. Errors: ' + ErrorMessages.Text); finally ErrorMessages.Free; end; end; function GetPagesViaObjectSearch(Doc: TPDFDocument): TPageArray; var i: Integer; Obj: TPDFObject; KidsArray: TPDFArray; PageList: TList; CandidateObjects: TList; begin CandidateObjects := TList.Create; PageList := TList.Create; try // Find all objects that could be Pages objects for i := 0 to Doc.Objects.Count - 1 do begin Obj := Doc.Objects[i]; if (Obj <> nil) and (Obj.GetValue('/Type') = '/Pages') and Obj.HasKey('/Kids') then begin CandidateObjects.Add(Obj); end; end; // Try each candidate Pages object for i := 0 to CandidateObjects.Count - 1 do begin Obj := TPDFObject(CandidateObjects[i]); KidsArray := Obj.GetArray('/Kids'); if (KidsArray <> nil) and (KidsArray.Count > 0) then begin // Validate that this Kids array contains actual pages if ValidateKidsArray(KidsArray) then begin PageList.Clear; TraversePagesTree(Obj, PageList); if PageList.Count > 0 then begin // Found valid pages - convert to array and return SetLength(Result, PageList.Count); for j := 0 to PageList.Count - 1 do Result[j] := TPDFObject(PageList[j]); Exit; end; end; end; end; // No valid Pages object found SetLength(Result, 0); finally CandidateObjects.Free; PageList.Free; end; end; |
Stratégies d'optimisation des performances.
Lors du traitement de fichiers PDF volumineux ou du traitement d'un grand volume de documents, les performances deviennent un facteur critique.
Chargement différé et mise en cache.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// Performance-optimized page access with caching type TPDFPageCache = class private FPages: array of TPDFPage; FPageObjects: array of TPDFObject; FCacheHits: Integer; FCacheMisses: Integer; FMaxCacheSize: Integer; public constructor Create(MaxCacheSize: Integer = 100); destructor Destroy; override; function GetPage(Index: Integer): TPDFPage; procedure ClearCache; procedure GetCacheStatistics(out Hits, Misses: Integer); end; function TPDFPageCache.GetPage(Index: Integer): TPDFPage; begin // Check if page is already cached if (Index >= 0) and (Index < Length(FPages)) and (FPages[Index] <> nil) then begin Inc(FCacheHits); Result := FPages[Index]; Exit; end; Inc(FCacheMisses); // Load page from object if not cached if (Index >= 0) and (Index < Length(FPageObjects)) and (FPageObjects[Index] <> nil) then begin Result := TPDFPage.CreateFromObject(FPageObjects[Index]); // Cache the page if we have room if Length(FPages) < FMaxCacheSize then begin if Index >= Length(FPages) then SetLength(FPages, Index + 1); FPages[Index] := Result; end; end else begin Result := nil; end; end; |
Traitement en streaming pour les documents volumineux.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Streaming approach for processing large PDF documents procedure ProcessLargePDFInChunks(const FileName: string; ChunkSize: Integer = 50); var Doc: TPDFDocument; TotalPages: Integer; ChunkStart, ChunkEnd: Integer; i: Integer; begin Doc := TPDFDocument.Create; try Doc.LoadFromFile(FileName); TotalPages := Doc.GetPageCount; LogMessage(Format('Processing %d pages in chunks of %d', [TotalPages, ChunkSize])); ChunkStart := 0; while ChunkStart < TotalPages do begin ChunkEnd := Min(ChunkStart + ChunkSize - 1, TotalPages - 1); LogMessage(Format('Processing chunk: pages %d-%d', [ChunkStart + 1, ChunkEnd + 1])); // Process this chunk of pages for i := ChunkStart to ChunkEnd do begin ProcessSinglePage(Doc, i); end; // Optional: Force garbage collection between chunks if (ChunkStart mod (ChunkSize * 4)) = 0 then begin ForceGarbageCollection; end; ChunkStart := ChunkEnd + 1; end; finally Doc.Free; end; end; |
Analyse avancée de la structure des fichiers PDF.
Pour les développeurs travaillant avec des exigences complexes de traitement de fichiers PDF, la compréhension des éléments structurels avancés est essentielle.
Héritage de pages et gestion des ressources.
Les pages PDF peuvent hériter de propriétés de leurs objets Pages parents, créant ainsi un système hiérarchique de gestion des ressources :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
% Example of page inheritance in PDF structure 2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 /MediaBox [0 0 612 792] /Resources << /Font << /F1 10 0 R >> /ProcSet [/PDF /Text] >> >> endobj % Child page inherits MediaBox and Resources from parent 3 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R >> % This page inherits MediaBox [0 0 612 792] and Resources from parent endobj % Child page overrides inherited MediaBox 4 0 obj << /Type /Page /Parent 2 0 R /Contents 6 0 R /MediaBox [0 0 792 612] >> % This page overrides MediaBox but still inherits Resources endobj |
Gestion de l'héritage de pages dans le code.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// Proper handling of page inheritance function GetEffectivePageProperties(PageObj: TPDFObject): TPDFPageProperties; var CurrentObj: TPDFObject; MediaBox: TPDFArray; Resources: TPDFObject; begin // Initialize result Result := TPDFPageProperties.Create; // Walk up the parent chain to collect inherited properties CurrentObj := PageObj; while CurrentObj <> nil do begin // Check for MediaBox at this level if Result.MediaBox.IsEmpty then begin MediaBox := CurrentObj.GetArray('/MediaBox'); if MediaBox <> nil then Result.MediaBox := MediaBox; end; // Check for Resources at this level if Result.Resources = nil then begin Resources := CurrentObj.GetDictionary('/Resources'); if Resources <> nil then Result.Resources := Resources; end; // Check for other inheritable properties CheckForInheritableProperty(CurrentObj, '/Rotate', Result.Rotate); CheckForInheritableProperty(CurrentObj, '/CropBox', Result.CropBox); // Move to parent object CurrentObj := CurrentObj.GetIndirectObject('/Parent'); // Prevent infinite loops in corrupted PDFs if CurrentObj = PageObj then break; end; // Validate that we found required properties if Result.MediaBox.IsEmpty then raise Exception.Create('No MediaBox found in page inheritance chain'); end; |
Stratégies de test pour l'ordre des pages PDF.
Des tests approfondis sont essentiels lors du traitement de l'ordre des pages PDF, compte tenu de la variété des structures de documents possibles.
Création de suites de tests complètes.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Comprehensive PDF test case generation script # Test Case 1: Sequential pages (baseline) echo "Creating sequential page test..." pdftk A=template.pdf cat A A A output test-sequential.pdf # Test Case 2: Non-sequential object IDs echo "Creating non-sequential object ID test..." pdftk A=page3.pdf B=page1.pdf C=page2.pdf cat A B C output test-nonsequential.pdf # Test Case 3: Hierarchical page tree echo "Creating hierarchical page tree test..." # This requires custom PDF generation tool generate-hierarchical-pdf --depth 3 --pages-per-node 2 output test-hierarchical.pdf # Test Case 4: Large document with mixed structures echo "Creating large document test..." pdftk A=large-doc.pdf cat 1-100 50-149 200-299 output test-large-mixed.pdf # Test Case 5: Corrupted page tree echo "Creating corrupted page tree test..." # This requires custom corruption tool corrupt-pdf-structure --target pages-tree test-sequential.pdf test-corrupted.pdf # Test Case 6: Minimal single-page document echo "Creating minimal single-page test..." pdftk A=template.pdf cat 1 output test-single-page.pdf |
Cadre de validation automatisé.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
// Comprehensive PDF page ordering validation framework type TPDFTestCase = record FileName: string; ExpectedPageCount: Integer; ExpectedPageOrder: array of Integer; Description: string; end; function RunPDFPageOrderingTests: Boolean; var TestCases: array of TPDFTestCase; i: Integer; PassCount, FailCount: Integer; begin // Define test cases SetLength(TestCases, 6); TestCases[0].FileName := 'test-sequential.pdf'; TestCases[0].ExpectedPageCount := 3; TestCases[0].ExpectedPageOrder := [0, 1, 2]; TestCases[0].Description := 'Sequential page ordering'; TestCases[1].FileName := 'test-nonsequential.pdf'; TestCases[1].ExpectedPageCount := 3; TestCases[1].ExpectedPageOrder := [2, 0, 1]; // Based on how pdftk reorders TestCases[1].Description := 'Non-sequential object IDs'; // ... define other test cases ... PassCount := 0; FailCount := 0; WriteLn('Running PDF page ordering tests...'); WriteLn('=' * 50); for i := 0 to High(TestCases) do begin Write(Format('Test %d: %s... ', [i + 1, TestCases[i].Description])); if ValidateTestCase(TestCases[i]) then begin WriteLn('PASS'); Inc(PassCount); end else begin WriteLn('FAIL'); Inc(FailCount); end; end; WriteLn('=' * 50); WriteLn(Format('Results: %d passed, %d failed', [PassCount, FailCount])); Result := FailCount = 0; end; function ValidateTestCase(const TestCase: TPDFTestCase): Boolean; var Doc: TPDFDocument; ActualPages: TPageArray; i: Integer; begin Result := False; Doc := TPDFDocument.Create; try if not Doc.LoadFromFile(TestCase.FileName) then begin WriteLn(Format('Failed to load %s', [TestCase.FileName])); Exit; end; ActualPages := GetPagesInCorrectOrder(Doc); // Validate page count if Length(ActualPages) <> TestCase.ExpectedPageCount then begin WriteLn(Format('Page count mismatch: expected %d, got %d', [TestCase.ExpectedPageCount, Length(ActualPages)])); Exit; end; // Validate page order (simplified - in real implementation, // you'd compare actual page content or identifiers) for i := 0 to High(ActualPages) do begin if not ValidatePageAtPosition(ActualPages[i], TestCase.ExpectedPageOrder[i]) then begin WriteLn(Format('Page order mismatch at position %d', [i])); Exit; end; end; Result := True; finally Doc.Free; end; end; |
Assurer la pérennité de votre code de traitement PDF.
Au fur et à mesure que les normes PDF évoluent et que de nouveaux cas d'utilisation apparaissent, il est important d'écrire du code qui puisse s'adapter aux exigences futures.
Conception pour l'extensibilité.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// Extensible PDF page processing architecture type IPDFPageProcessor = interface ['{12345678-1234-1234-1234-123456789012}'] function ProcessPage(Page: TPDFPage; Context: TPDFProcessingContext): Boolean; function GetProcessorName: string; function GetSupportedPDFVersions: TStringArray; end; TPDFProcessingPipeline = class private FProcessors: TList; FContext: TPDFProcessingContext; public constructor Create; destructor Destroy; override; procedure RegisterProcessor(Processor: IPDFPageProcessor); procedure UnregisterProcessor(Processor: IPDFPageProcessor); function ProcessDocument(Doc: TPDFDocument): Boolean; end; function TPDFProcessingPipeline.ProcessDocument(Doc: TPDFDocument): Boolean; var Pages: TPageArray; i, j: Integer; Page: TPDFPage; Processor: IPDFPageProcessor; Success: Boolean; begin Result := True; // Get pages in correct order using our robust method Pages := GetPagesInCorrectOrder(Doc); // Process each page through all registered processors for i := 0 to High(Pages) do begin Page := TPDFPage.CreateFromObject(Pages[i]); try FContext.CurrentPageIndex := i; FContext.TotalPages := Length(Pages); for j := 0 to FProcessors.Count - 1 do begin Processor := FProcessors[j]; Success := Processor.ProcessPage(Page, FContext); if not Success then begin LogError(Format('Processor %s failed on page %d', [Processor.GetProcessorName, i + 1])); Result := False; // Continue with other processors/pages or break based on policy end; end; finally Page.Free; end; end; end; |
L'investissement dans une bonne compréhension de la structure PDF se traduit par une réduction des coûts de support, une amélioration de la satisfaction des utilisateurs et une maintenance plus facile tout au long de la durée de vie de l'application. L'ordre des pages PDF n'est pas qu'un simple détail technique, mais un aspect fondamental de l'intégrité des documents qui a un impact direct sur l'expérience utilisateur. Maîtrisez cette complexité, et vous créerez des applications PDF que les utilisateurs pourront utiliser en toute confiance pour leurs documents les plus importants.