Przejdź do głównej zawartości
getnextpdf.com

PDF jest kontenerem: pliki osadzone i dane powiązane

Spec: ISO 32000-2, §7.11.4Spec: ISO 32000-2, §14.13Spec: ISO 19005-3, PDF/A-3

Większość ludzi wyobraża sobie PDF jako stos stron. To część, którą widzisz. Ale PDF jest też kontenerem i może nosić w sobie całe inne pliki — arkusz kalkulacyjny, ładunek XML, oryginalny dokument źródłowy — zapakowane w ten sam pojedynczy plik, który wręczasz komuś innemu.

Ta strona wyjaśnia, jak to działa: strumień pliku osadzonego, który przechowuje bajty, drzewo nazw, które je wymienia, oraz jeden klucz, który decyduje o tym, czy załącznik po prostu tam siedzi, czy faktycznie coś znaczy.

Nieotypowany załącznik i otypowany wyglądają dla człowieka identycznie. Oba to plik jadący wewnątrz PDF i — w tym silniku — oba są powiązane z dokumentem. Różnica polega na tym, że jeden z nich mówi maszynie, do czego służy, a drugi pozostawia relację pustą, by maszyna zgadywała.

Ta różnica to cała gra dla hybrydowej e-faktury. Platforma podatkowa nie czyta strony twojej faktury; czyta XML, który osadziłeś. Jeśli ten XML jest dołączony jako nieokreślony bloob, a nie jako dane faktury dla widocznego dokumentu, zgodny czytnik nie ma wiarygodnego sposobu, by wiedzieć, że to ładunek do przetworzenia. Strona wygląda idealnie. Faktura zostaje odrzucona. Niepowodzenie przychodzi po kilku dniach, a za nim wstrzymana płatność.

Ustawienie właściwej relacji w warstwie, która wytwarza plik, jest znacznie tańsze niż odkrywanie tego po jednej odrzuconej fakturze na raz.

  • PDF może osadzić bajty dowolnego pliku jako strumień pliku osadzonego (Spec: ISO 32000-2, §7.11.4). Strumień niesie dane plus niewielki słownik parametrów: oryginalny rozmiar, daty oraz sumę kontrolną.
  • Pliki osadzone są skatalogowane w drzewie nazw EmbeddedFiles, dzięki czemu czytnik może je wyliczyć po nazwie bez przeglądania całego dokumentu.
  • Plik powiązany idzie o krok dalej: deklaruje AFRelationship (Spec: ISO 32000-2, §7.11.3) — jedną z ośmiu standardowych wartości (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) lub wartość niestandardową — mówiąc, jak plik odnosi się do treści, do której jest dołączony.
  • Ta otypowana relacja to mechanizm stojący za hybrydowymi e-fakturami (ZUGFeRD / Factur-X) oraz załącznikami PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF wspiera surowe prymitywy kontenera w edycji podstawowej: embedFile() oraz embedFileFromString() z jawną relacją. Edycje zaawansowane dodają na bazie tych prymitywów dedykowany osadzacz e-faktur EN 16931 / ZUGFeRD / Factur-X.

Pomyśl o tym jak o dwóch warstwach ułożonych jedna na drugiej.

Niższa warstwa to przechowywanie. Strumień pliku osadzonego (Spec: ISO 32000-2, §7.11.4) to bajty oryginalnego pliku opakowane w obiekt strumienia PDF, ze słownikiem parametrów zapisującym oryginalny rozmiar, datę modyfikacji oraz sumę kontrolną nieskompresowanych danych. Do strumienia dociera się przez słownik specyfikacji pliku, którego słownik /EF wskazuje na strumień pliku osadzonego — sam strumień nie niesie /EF. Czytnik może wyciągnąć plik z powrotem bajt po bajcie. Aby te pliki dało się odnaleźć, katalog dokumentu trzyma drzewo nazw EmbeddedFiles — posortowaną mapę od nazwy do każdej specyfikacji pliku — dzięki czemu czytnik może wymienić „oto 3 pliki wewnątrz tego PDF” bez przechodzenia przez każdą stronę.

Wyższa warstwa to znaczenie. Sam z siebie plik osadzony jest po prostu obecny. Mechanizm plików powiązanych (Spec: ISO 32000-2, §14.13) dołącza plik do czegoś — całego dokumentu, strony, obiektu graficznego — i stempluje go za pomocą AFRelationship. ISO 32000-2 definiuje niewielki słownik ośmiu standardowych wartości (Spec: ISO 32000-2, §7.11.3) i dopuszcza też wartości niestandardowe; każda standardowa wartość odpowiada na precyzyjne pytanie:

AFRelationshipCo stwierdza o pliku
SourceTo materiał źródłowy, z którego wygenerowano widoczną treść (na przykład oryginalny dokument edytora tekstu).
DataTo ustrukturyzowane dane powiązane z widoczną treścią — kanonicznym przypadkiem jest XML faktury stojący za wyrenderowaną stroną faktury.
AlternativeTo alternatywna reprezentacja tej samej treści (na przykład wersja audio lub wideo).
SupplementTo materiał uzupełniający, który rozszerza treść, ale nie jest jej częścią.
EncryptedPayloadPlik osadzony to zaszyfrowany ładunek, który PDF opakowuje jako nieprzejrzysty bloob.
FormDataPlik to dane formularza (FDF, XFDF lub ładunek formularza XML).
SchemaPlik to schemat opisujący strukturę pliku Data (na przykład XSD dla danych XML albo JSON Schema).
UnspecifiedRelacja jest celowo nieokreślona. Uczciwie, ale nie mówi maszynie niczego.

Poza tymi ośmioma standard dopuszcza również niestandardowe wartości relacji charakterystyczne dla aplikacji, więc słownik jest rozszerzalny, a nie zamknięty.

Plik powiązany jest zdefiniowany przez dwie rzeczy działające razem, a nie przez sam jeden klucz. Powiązanie /AF wiąże specyfikację pliku z częścią dokumentu; klucz AFRelationship w specyfikacji pliku następnie stwierdza relację semantyczną. Wpis /AF w punkcie powiązania (katalog dokumentu, strona lub obiekt) to tablica — ta tablica zawiera jedną lub więcej słowników specyfikacji pliku, zwykle jako odwołania pośrednie; /AF nie jest pojedynczym odwołaniem. Plik powiązany na poziomie dokumentu to specyfikacja pliku wymieniona w tablicy /AF katalogu dokumentu, niosąca swój AFRelationship. Oznacz ten arkusz jako Unspecified, a powiążesz go z dokumentem, ale nie powiesz maszynie niczego o tym, dlaczego. Oznacz ten sam arkusz jako Data, a powiesz każdemu zgodnemu czytnikowi, czym jest i do czego służy. Bajty są takie same. Semantyka nie.

Dlatego przypadek e-faktury nie sprowadza się do „dołącz plik XML”. Brzmi on „osadź ten XML jako plik powiązany Data dla tego dokumentu, wewnątrz zgodnego nośnika PDF/A-3” — przy czym ważność faktury i jej akceptacja prawna pozostają osobnymi kontrolami, których nośnik nie wykonuje. Przepływ ma cztery etapy, a kolejność jest tym, co utrzymuje go w poprawności.

  1. Store the bytesThe file is wrapped in an embedded file stream with its size, dates, and a checksum (ISO 32000-2 §7.11.4).
  2. Register it by nameThe file specification is added to the EmbeddedFiles name tree so a reader can enumerate attachments without scanning the document.
  3. Declare the relationshipAn AFRelationship value (one of the eight standard values such as Source or Data) marks how the file relates to the content, associated at the document level (ISO 32000-2 §14.13.3).
  4. Make it archivalA PDF/A-3 carrier permits the embedded payload to ride inside one conforming archival PDF/A document; invoice validity and legal acceptance remain separate checks (ISO 19005-3).
Jak otypowany załącznik staje się plikiem hybrydowym od początku do końca: silnik przechowuje bajty, rejestruje plik po nazwie, deklaruje relację, a profil archiwalny pozwala temu wszystkiemu jechać wewnątrz jednego zgodnego dokumentu archiwalnego.

Ten czwarty etap to powód, dla którego PDF/A-3 istnieje jako odrębny profil. Wcześniejsze profile archiwalne ograniczały to, co można było osadzić; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) to ta część, która pozwala, by pliki dowolnego formatu jechały wewnątrz zgodnego dokumentu archiwalnego. Pozwala na osadzony ładunek — nie waliduje tego ładunku ani nie nadaje statusu prawnego. Bez niego faktura hybrydowa — jeden plik będący jednocześnie stroną, którą czyta człowiek, i danymi, które parsuje system podatkowy — w ogóle nie mogłaby być zgodnym dokumentem archiwalnym PDF/A; to, czy faktura jest ważna i prawnie akceptowana, pozostaje osobnym pytaniem. Dedykowany osadzacz e-faktur, który dodają edycje zaawansowane, to wygodny szew nad dokładnie tym: osadza ładunek, ustawia relację na Data i poprawnie go rejestruje, dzięki czemu nie składasz instalacji kontenera ręcznie. Głębsze mechanizmy fakturowania i archiwizacji żyją na dwóch sąsiednich stronach, do których linki są poniżej; ta strona dotyczy kontenera, na którym obie się opierają.

Mały, kompletny program. Dwa wywołania, które mają znaczenie, to różnica między nieotypowanym plikiem powiązanym a otypowanym — a relacja to jawny argument, który powinieneś ustawić. W tym silniku oba wywołania wytwarzają plik powiązany: embedFile() oraz embedFileFromString() zawsze rejestrują specyfikację pliku w tablicy /AF katalogu dokumentu, więc jedyną rzeczą, którą zmienia relacja, jest to, co oznacza powiązanie. Domyślnie przyjmuje wartość Unspecified, która powiązuje plik, ale nie mówi maszynie niczego o tym, dlaczego; dla ładunku e-faktury ustawiasz ją na Data, aby czytnik mógł go odnaleźć.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Navigation\AFRelationship;
$document = Document::createStandalone();
$document->addPage();
$document->setFont('helvetica', 'B', 16);
$document->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// An UNTYPED associated file: the bytes are embedded AND the file spec is
// added to the document catalog's /AF array, but the relationship says
// nothing about why. A reader can open it; a machine cannot tell its role.
// The relationship is left Unspecified (its default); the second argument is
// the human-readable description. embedFile accepts the AFRelationship enum.
$document->embedFile(
'/srv/invoices/INV-2026-0042-source.docx',
'Original source document',
AFRelationship::Unspecified,
);
// A TYPED associated file: the invoice XML is declared as the DATA behind
// the visible page. This is the relationship a hybrid e-invoice reader
// looks for — the same intent the dedicated e-invoice embedder sets.
// embedFileFromString takes the data, a filename, a description, and a
// relationship as a PDF-name string ('/Data').
$invoiceXml = $generateCiiXml(); // your ERP authors this; the engine never does
$document->embedFileFromString(
$invoiceXml,
'factur-x.xml',
'Factur-X invoice data',
'/Data',
);
$bytes = $document->getPdfData();

Relacja '/Data' jest niewątpliwa. Pierwszy załącznik — pozostawiony jako Unspecified — jest powiązany tak samo, tylko bez stwierdzonego znaczenia. Dla obu wywołań silnik zapisuje strumień pliku osadzonego, dodaje plik do drzewa nazw EmbeddedFiles, wymienia jego specyfikację pliku w tablicy /AF katalogu dokumentu i zapisuje relację, którą stwierdziłeś — nie wybiera jej za ciebie. Ten silnik nie ma trybu z samym drzewem nazw: każdy plik, który osadzasz w ten sposób, jest plikiem powiązanym z dokumentem, więc relacja to jedyna dźwignia, którą kontrolujesz.

Częste założenie jest takie, że „osadzony” i „powiązany” to dwa słowa na to samo. Nie są. Osadzony dotyczy przechowywania — bajty są wewnątrz PDF. Powiązany dotyczy wiązania — specyfikacja pliku jest wymieniona w tablicy /AF w jakiejś części dokumentu i niesie AFRelationship. W abstrakcyjnym modelu PDF plik może być osadzony w drzewie nazw, nigdy nie będąc powiązanym; ścieżka embedFile() NextPDF nie pozostawia go w tym stanie — zawsze zapisuje powiązanie /AF — więc dla tego silnika otwartym pytaniem nigdy nie jest czy plik jest powiązany, lecz co mówi relacja.

Druga pułapka: założenie, że czytnik „domyśli się”, który załącznik jest fakturą. Zgodny czytnik nie powinien zgadywać. Szuka pliku, którego relacja mówi Data. Pozostaw relację jako Unspecified, a powiążesz ładunek, mówiąc maszynie nic użytecznego o jego roli.

Mechanizm kontenera jest potężny w sposób, o którym warto być uczciwym: embedFile() odczytuje dowolną ścieżkę, którą może odczytać proces PHP. To jest funkcja — i to jest też granica. Silnik dołącza bajty, które mu dano; nie decyduje, i nie może, za ciebie o tym, czy dana ścieżka jest tą, którą zamierzałeś ujawnić.

Embedding a file from a caller-supplied path — edition availability
EditionAvailability
Core

embedFile() reads any path the PHP process has access to and embeds its bytes verbatim. Validating that the path is safe and intended — not a user-controlled value, a traversal, or a secret outside the document’s scope — is the integrator’s responsibility. This is a documented security contract, not an oversight: the engine will not silently guess which paths are legitimate, because that guess belongs to your application, which knows the trust boundary the engine cannot see. Pass attacker-influenced bytes through a string with embedFileFromString() so the path layer is never in play.

ProNot in this edition
EnterpriseNot in this edition

Dwie kolejne granice, które warto wyłożyć wprost:

  • Osadzanie to nie walidacja. Silnik niesie bajty, które mu podasz. To, czy osadzony XML jest zgodnym ładunkiem faktury, to osobne pytanie, na które odpowiada walidator — zobacz stronę o fakturowaniu.
  • Otypowany załącznik sam w sobie nie jest zgodnym plikiem archiwalnym. Uczynienie pliku hybrydowego prawnym dokumentem PDF/A-3 wymaga trybu archiwalnego oraz niezależnej kontroli zgodności — zobacz stronę o archiwizacji.
  • Faktury i e-fakturowanie — przypadek użycia, który ten mechanizm umożliwia: hybrydowy PDF niosący odczytywalną maszynowo fakturę jako swój plik powiązany Data.
  • Archiwizacja i PDF/A — dlaczego nośnik to plik PDF/A-3 oraz co zgodność obiecuje, a czego nie.
  • Anatomia pliku PDF — gdzie w strukturze pliku znajdują się drzewo nazw i katalog dokumentu.
  • Strumienie i filtry — jak bajty pliku osadzonego są przechowywane i kompresowane wewnątrz obiektu strumienia.
  • Strumień pliku osadzonego — obiekt strumienia PDF przechowujący bajty zewnętrznego pliku, ze słownikiem parametrów zapisującym jego oryginalny rozmiar, daty oraz sumę kontrolną (ISO 32000-2 §7.11.4).
  • Drzewo nazw EmbeddedFiles — posortowana mapa w katalogu dokumentu, która wymienia pliki osadzone po nazwie, dzięki czemu czytnik może wyliczyć załączniki bez przeglądania całego dokumentu.
  • Plik powiązany — plik osadzony związany z częścią dokumentu powiązaniem /AF (w katalogu dokumentu, na stronie lub na obiekcie) i niosący AFRelationship, który stwierdza, jak odnosi się do tej treści; przypadek na poziomie dokumentu — specyfikacja pliku w tablicy /AF katalogu — to ten, na którym skupia się ta strona (ISO 32000-2 §14.13.3).
  • AFRelationship — klucz specyfikacji pliku, którego wartość nazywa relację (ISO 32000-2 §7.11.3). Przyjmuje jedną z ośmiu standardowych wartości (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) lub wartość niestandardową; Data to wartość, której używa ładunek hybrydowej e-faktury.
  • PDF/A-3 — profil archiwalny ISO 19005-3, który pozwala osadzać pliki dowolnego formatu, umożliwiając zgodny dokument hybrydowy.
  • Faktura hybrydowa — jeden plik PDF, który jest jednocześnie czytelną dla człowieka stroną oraz odczytywalnym maszynowo osadzonym ładunkiem faktury.