Ukryta złożoność struktury PDF
PDF dokumenty są o wiele bardziej wyrafinowane, niż się wydaje użytkownikom końcowym. Podczas gdy przeglądający widzą strony w logicznej, sekwencyjnej kolejności (1, 2, 3…), wewnętrzna architektura pliku PDF opowiada zupełnie inną historię. Ta złożoność jest jednym z najbardziej źle rozumianych aspektów przetwarzania PDF, prowadzącym do niezliczonych błędów, nieprawidłowych implementacji i sfrustrowanych programistów. Ten obszerny artykuł bada skomplikowany świat organizacji stron PDF, wyjaśnia, dlaczego programiści często napotykają nieoczekiwane problemy z kolejnością stron i zapewnia praktyczne rozwiązania w zakresie niezawodnej manipulacji PDF.
Model obiektowy PDF: zmiana paradygmatu z dokumentów sekwencyjnych
Aby zrozumieć wyzwania związane z porządkowaniem stron PDF, musimy najpierw zrozumieć, jak zasadniczo różni się PDF od prostszych formatów dokumentów. W przeciwieństwie do zwykłych plików tekstowych, dokumentów HTML lub nawet starszych formatów, takich jak RTF, PDF wykorzystuje wyrafinowaną architekturę opartą na obiektach, w której organizacja treści i fizyczne przechowywanie są całkowicie oddzielone.
Ta decyzja architektoniczna została podjęta z kilku ważnych powodów:
- Elastyczność: Można odwoływać się do obiektów z wielu lokalizacji bez duplikowania
- Wydajność: Wspólne zasoby (czcionki, obrazy, stany graficzne) mogą być współużytkowane pomiędzy stronami
- Aktualizacje przyrostowe: Dokumenty można modyfikować bez przepisywania całego pliku
- Dostęp losowy: Przeglądający mogą przejść do dowolnej strony bez analizowania całego dokumentu
Jednakże ta elastyczność odbywa się kosztem złożoności, szczególnie jeśli chodzi o zrozumienie związku pomiędzy kolejnością przechowywania obiektów a logiczną sekwencją stron.
Odniesienia do obiektów a kolejność wyświetlania: konkretny przykład
Rozważmy typową strukturę PDF, która ilustruje rozłączenie pomiędzy przechowywaniem a wyświetlaniem:
Zakreślacz składni Urvanov v2.9.1|
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 |
W tym przykładzie obiekty strony są przechowywane jako obiekty 4 i 20, ale kolejność wyświetlania jest określona przez tablicę Kids: [20, 1, 4]. Spowoduje to utworzenie następującego mapowania:
- Strona 1 (kolejność wyświetlania) = Obiekt 20 (kolejność przechowywania: ostatnia)
- Strona 2 (kolejność wyświetlania) = Obiekt 1 (kolejność przechowywania: pierwsza)
- Strona 3 (kolejność wyświetlania) = Obiekt 4 (kolejność przechowywania: trzecia)
To rozłączenie nie jest przypadkowe — to podstawowa cecha PDF, która umożliwia wyrafinowaną manipulację dokumentami i optymalizację.
Dlaczego generatory PDF tworzą niesekwencyjne zamówienia obiektów
Zrozumienie, dlaczego generatory PDF tworzą niesekwencyjne porządki obiektów, pomaga programistom docenić złożoność, z jaką mają do czynienia, i uniknąć podejmowania błędnych założeń na temat struktury dokumentu.
PDF Procesy tworzenia
Różne procesy tworzenia PDF skutkują różnymi wzorcami porządkowania obiektów:
1. Sekwencyjne tworzenie dokumentu
Zakreślacz składni Urvanov v2.9.1|
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. Zoptymalizowane udostępnianie zasobów
Zakreślacz składni Urvanov v2.9.1|
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. Przyrostowe składanie dokumentu
Zakreślacz składni Urvanov v2.9.1|
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 |
Typowe błędy programistów i ich konsekwencje
Złożoność struktury PDF prowadzi do kilku typowych błędów, które mogą mieć poważne konsekwencje dla niezawodności aplikacji i doświadczenia użytkownika.
Błąd 1: Założenie, że kolejność identyfikatorów obiektów jest równa kolejności wyświetlania
Jest to prawdopodobnie najczęstszy błąd popełniany przez programistów, którzy dopiero rozpoczynają przetwarzanie PDF:
Zakreślacz składni Urvanov v2.9.1|
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; |
Konsekwencje tego błędu obejmują:
- Strony pojawiają się w dokumentach wyjściowych w nieprawidłowej kolejności
- Numeracja stron staje się niespójna
- Zamieszanie użytkowników i prośby o pomoc
- Potencjalne uszkodzenie danych w potokach przetwarzania dokumentów
Błąd 2: zakodowane na stałe mapowanie strony na podstawie obserwacji
Kiedy programiści napotykają problemy z kolejnością stron, czasami wdrażają na stałe zakodowane poprawki w oparciu o zaobserwowane wzorce:
Zakreślacz składni Urvanov v2.9.1|
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; |
To podejście jest zasadniczo błędne, ponieważ:
- Działa tylko w przypadku określonych plików PDF zaobserwowanych podczas programowania
- Katastrofalnie kończy się niepowodzeniem w przypadku plików PDF o różnych strukturach
- Tworzy nieprzewidywalne zachowanie, którego użytkownicy nie mogą zrozumieć
- Gromadzi dług techniczny w miarę dodawania większej liczby przypadków specjalnych
Błąd 3: Ignorowanie hierarchicznych drzew stron
Wielu programistów zakłada, że drzewa stron PDF są zawsze tablicami płaskimi, ale specyfikacja PDF pozwala na struktury hierarchiczne:
Zakreślacz składni Urvanov v2.9.1|
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; |
Właściwe podejście: przestrzeganie struktury drzewa stron
Właściwy sposób obsługi kolejności stron PDF polega na zaimplementowaniu pełnego przeglądania drzewa Pages, które jest dokładnie zgodne ze specyfikacją PDF.
Zrozumienie hierarchii drzewa stron
PDF drzewa stron mogą być hierarchiczne, z pośrednimi obiektami Pages zawierającymi własne tablice Kids:
Zakreślacz składni Urvanov v2.9.1|
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 |
Implementacja rekurencyjnego przechodzenia przez drzewo stron
Zakreślacz składni Urvanov v2.9.1|
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; |
Obsługa rzeczywistych PDF odmian i przypadków Edge
Rzeczywiste pliki PDF często odbiegają od idealnej struktury opisanej w specyfikacji. Solidna biblioteka przetwarzania PDF musi sprawnie obsługiwać te różnice.
Typowe anomalie strukturalne
1. Brakujący lub uszkodzony katalog
Zakreślacz składni Urvanov v2.9.1|
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. Odniesienia cykliczne
Zakreślacz składni Urvanov v2.9.1|
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. Niespójne wartości liczników
Zakreślacz składni Urvanov v2.9.1|
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 |
Implementacja niezawodnej obsługi błędów
Zakreślacz składni Urvanov v2.9.1|
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; |
Strategie optymalizacji wydajności
Podczas przetwarzania dużych plików PDF lub przetwarzania dużej liczby dokumentów wydajność staje się czynnikiem krytycznym.
Leniwe ładowanie i buforowanie
Zakreślacz składni Urvanov v2.9.1|
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; |
Przetwarzanie strumieniowe dużych dokumentów
Zakreślacz składni Urvanov v2.9.1|
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; |
Zaawansowana analiza struktury PDF
Dla programistów pracujących ze złożonymi wymaganiami dotyczącymi przetwarzania PDF zrozumienie zaawansowanych elementów konstrukcyjnych ma kluczowe znaczenie.
Dziedziczenie stron i zarządzanie zasobami
PDF strony mogą dziedziczyć właściwości ze swoich nadrzędnych obiektów Pages, tworząc hierarchiczny system zarządzania zasobami:
Zakreślacz składni Urvanov v2.9.1|
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 |
Obsługa dziedziczenia stron w kodzie
Zakreślacz składni Urvanov v2.9.1|
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; |
Strategie testowania dla zamawiania stron PDF
Kompleksowe testowanie jest niezbędne przy porządkowaniu stron PDF, biorąc pod uwagę różnorodność możliwych struktur dokumentów.
Tworzenie kompleksowych zestawów testów
Zakreślacz składni Urvanov v2.9.1|
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 |
Struktura automatycznej walidacji
Zakreślacz składni Urvanov v2.9.1|
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; |
Przyszłościowy kod przetwarzania PDF
W miarę ewolucji standardów PDF i pojawiania się nowych przypadków użycia, ważne jest napisanie kodu, który będzie można dostosować do przyszłych wymagań.
Projektowanie pod kątem rozszerzalności
Zakreślacz składni Urvanov v2.9.1|
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; |
Inwestycja we właściwe zrozumienie struktury PDF przynosi korzyści w postaci zmniejszonego obciążenia wsparcia, poprawy zadowolenia użytkowników i łatwiejszej konserwacji przez cały okres użytkowania aplikacji. PDF kolejność stron to nie tylko szczegół techniczny – to podstawowy aspekt integralności dokumentu, który bezpośrednio wpływa na wygodę użytkownika. Opanuj tę złożoność, a zbudujesz aplikacje PDF, którym użytkownicy będą mogli powierzyć najważniejsze dokumenty.