Zestaw rysunków przychodzi z wymiarami na osobnej warstwie, a oczywisty sposób ich ukrycia to przeniesienie odwołania tej grupy z tablicy /ON domyślnej konfiguracji do /OFF. Plik zapisuje się i otwiera ponownie, a linie wymiarowe nadal są drukowane przez tabliczkę rysunku. Przyczyna siedzi o obiekt dalej, wewnątrz słownika grupy: /Usage << /View << /ViewState /ON >> >> — stan jawny, który renderer czyta przed tablicami konfiguracji
Stan warstwy może mieszkać w kilku miejscach PDF-a, a edycja /OCProperties dotyka tylko jednego z nich. Silnik PDFium od Google renderuje zawartość opcjonalną poprawnie, ale jego publiczne API C nie ma funkcji do enumerowania grup zawartości opcjonalnej (OCG), wybierania konfiguracji albo włączania i wyłączania grupy. Kod Delphi sterujący warstwami musi sam przepisać obiekty i odtworzyć dokładnie kolejność decyzji renderera, albo jego lista warstw raportuje jeden stan, a strona pokazuje inny
Dlaczego edycja /ON i /OFF nie zmienia tego, co renderuje PDFium?
Tablice /ON i /OFF to dopiero drugi z pięciu kroków, a dwa późniejsze kroki potrafią je obalić. ISO 32000-1 §8.11.4.3 definiuje słownik konfiguracji zawartości opcjonalnej z /BaseState, /ON, /OFF, /Locked, /RBGroups i /AS, podczas gdy §8.11.4.4 definiuje słownik Usage, który OCG może nieść. W specyfikacji Usage opisuje, do czego grupa służy, a wpisy auto-stanu w /AS zamieniają ten opis w ON albo OFF. PDFium idzie krótszą drogą: gdy renderuje w jakimś celu, własny jawny stan grupy dla tego celu wygrywa. Złożone razem, kolejność wygląda tak:
- Startuj od
/BaseStatekonfiguracji, gdzie/ONi/Unchangedliczą się oba jako widoczne, a tylko/OFFukrywa - Zastosuj tablicę
/ON, potem tablicę/OFF, więc grupa wymieniona w obu ląduje ukryta - Zastosuj jawny stan Usage grupy dla żądanego celu, jak
/Usage << /View << /ViewState /OFF >> >>, który nadpisuje wszystko powyżej - Grupę, której
/Intentnie zawiera ani/View, ani/All, traktuj jako widoczną, bo nie bierze udziału w widoczności z intencją widoku - Tablicę
/ASwybranej konfiguracji puść ostatnią, pozwalając wpisom pasującego zdarzenia ustawić stan grup, które wymieniają
Krok trzeci wyjaśnia plik z otwarcia: niektóre exporterzy zapisują /ViewState /ON w każdej tworzonej grupie i od tej chwili żadna ilość edycji tablic nie ukryje tych warstw. Druk i eksport podążają tą samą regułą z jednym skrętem: renderer czyta najpierw /PrintState albo /ExportState i spada na /ViewState, gdy konkretnego wpisu brak. Samotne /ViewState /ON przypina warstwę więc i na papierze. Krok czwarty jest ciszej. Grupa otagowana /Intent /Design jest całkiem poza widocznością z intencją widoku i maluje się niezależnie od tablic, podczas gdy grupa bez klucza /Intent jest traktowana jako /View — domyślny, który daje specyfikacja
Wylistowanie warstw i ich stanu renderowania przez InspectOptionalContent
PDFium Component odtwarza tę kolejność w TPdf.InspectOptionalContent. Metoda wypełnia TPdfOptionalContentInventory i zwraca True przy sukcesie; parametr Usage ma domyślnie ocuView, a parametr ConfigurationIndex domyślnie -1, co wybiera indeks 0. Indeks konfiguracji 0 to domyślny słownik /D, a wpisy /Configs ciągną się od indeksu 1 w kolejności plikowej. Przed skanowaniem metoda każe PDFium zapisać otwarty dokument do pamięci i rozwija strumienie obiektów, więc edycje zrobione wcześniej w sesji pokazują się w wyniku. Plik bez /OCProperties albo indeks konfiguracji poza dostępnym zakresem każe wywołaniu zwrócić False z powodem w ErrorMessage; wyjątek nie jest podnoszony w żadnym z tych przypadków
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
Cfg: TPdfOptionalContentConfiguration;
G: TPdfOptionalContentGroup;
begin
// Usage domyślnie ocuView; -1 wybiera konfigurację 0, słownik /D
if not Pdf.InspectOptionalContent(Inv) then
begin
Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
Exit;
end;
Cfg := Inv.Configurations[Inv.SelectedConfigurationIndex];
Memo1.Lines.Add(Format('Configuration %d "%s", %d groups, /AS present: %s',
[Inv.SelectedConfigurationIndex, string(Cfg.Name), Length(Inv.Groups),
BoolToStr(Cfg.HasAutomaticState, True)]));
for G in Inv.Groups do
Memo1.Lines.Add(Format('obj %d %-24s visible=%s locked=%s radio=%d',
[G.ObjectNumber, string(G.Name),
BoolToStr(G.EffectiveVisible, True),
BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;
Każde TPdfOptionalContentGroup niesie numer obiektu, nazwę, Intents i trzy jawne stany ViewState, PrintState i ExportState, każdy z ocsUnspecified, ocsOn albo ocsOff. Gdy stan czyta ocsOn, a grupa siedzi w tablicy /OFF, znalazłeś pułapkę z akapitu otwierającego. Locked i RadioGroupIndex pochodzą z wybranej konfiguracji, a RadioGroupIndex wynosi -1 dla grupy, która nie należy do żadnego zestawu /RBGroups
Reszta słownika Usage jest raportowana tak, jak stanowi plik: Language i LanguagePreferred, UserType i UserNames, PageElementSubtype oraz MinimumZoom i MaximumZoom za flagą HasZoomRange. EffectiveVisible nie zależy od żadnego z nich. Wyświetlanie zależne od powiększenia to sprawa twojej przeglądarki. Każde TPdfOptionalContentConfiguration wystawia nazwę, BaseState, zawartość tablic i RadioGroups, plus OrderGroupObjectNumbers, które spłaszcza /Order w odwołania do grup i pomija etykiety tekstowe zagnieżdżonych tablic etykiet
Druk i eksport mogą się nie zgadzać z ekranem
Argument Usage zmienia odpowiedź i po to on istnieje. Pod ocuPrint inwentarz czyta /PrintState z jego fallbackiem na /ViewState, a udział biorą wyłącznie wpisy /AS, których /Event to /Print. Warstwa znaku wodnego zbudowana do pojawiania się tylko na papierze raportuje więc ukrytą pod ocuView i widoczną pod ocuPrint. Porównanie obu przebiegów znajduje takie warstwy, zanim wydruk kogokolwiek zaskoczy
procedure TFormMain.ReportPrintOnlyLayers;
var
OnScreen, OnPaper: TPdfOptionalContentInventory;
I: Integer;
begin
if not Pdf.InspectOptionalContent(OnScreen, ocuView) or
not Pdf.InspectOptionalContent(OnPaper, ocuPrint) then
Exit;
// Oba przebiegi czytają tę samą tablicę /OCGs, więc kolejność grup pasuje
for I := 0 to High(OnScreen.Groups) do
if OnScreen.Groups[I].EffectiveVisible <> OnPaper.Groups[I].EffectiveVisible then
Memo1.Lines.Add(Format('%s: screen=%s, print=%s',
[string(OnScreen.Groups[I].Name),
BoolToStr(OnScreen.Groups[I].EffectiveVisible, True),
BoolToStr(OnPaper.Groups[I].EffectiveVisible, True)]));
end;
Jak są oceniane wyrażenia widoczności OCMD?
Słownik członkostwa zawartości opcjonalnej (OCMD, ISO 32000-1 §8.11.2.2) wiąże fragment treści z kilkoma grupami przez politykę /P albo przez wyrażenie widoczności /VE, łączące grupy przez /And, /Or i /Not. Specyfikacja daje /P domyślny /AnyOn, pozwala /VE mieć pierwszeństwo przed /OCGs i /P, gdy jest obecne, i mówi, że OCMD bez grup nie ma wpływu na widoczność
Tablica Memberships inwentarza podąża za tymi regułami w oparciu o wyniki per grupa z tego samego przebiegu, więc usage, o który prosiłeś, przechodzi dalej. Każde członkostwo raportuje numery obiektów swoich grup, Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff albo ocmpUnknown dla nazwy spoza tej czwórki), surowy tekst VisibilityExpression i własne EffectiveVisible. OCMD bez grup liczy się jako widoczny. Odwołanie do numeru obiektu, który nie jest znanym OCG, też liczy się jako widoczne, więc jedno wiszące odwołanie nie ukrywa wszystkiego wokół. Ocena /VE zatrzymuje się na głębokości zagnieżdżenia 32 i traktuje wszystko głębsze jako ukryte, więc samoodwołujące się wyrażenie nie przepełni stosu. Skan wylistowuje każdy OCMD składowany jako obiekt pośredni z /Type /OCMD, strumienie obiektów włącznie
Zapisywanie nowego stanu warstw przez SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured bierze tablicę rekordów TPdfOptionalContentStateChange, z których każdy trzyma GroupObjectNumber i flagę Visible. Zapisuje kopię, w której wybrana konfiguracja produkuje dokładnie ten stan pod pięciokrokową kolejnością. Grupy, których nie wspomnisz, zachowują widoczność, którą policzył dla nich inwentarz
procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer;
ForPrint: Boolean);
var
Changes: TPdfOptionalContentStateChanges;
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
SetLength(Changes, 2);
Changes[0].GroupObjectNumber := DimensionsObj;
Changes[0].Visible := False;
Changes[1].GroupObjectNumber := NotesObj;
Changes[1].Visible := True;
Options := TPdfOptionalContentConfigureOptions.Default;
if ForPrint then
begin
Options.Usage := ocuPrint; // zapisuje /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // pierwszy wpis /Configs, /D zostaje takie, jakie było
end;
if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
Changes, Options, Report) then
raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);
Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
[Report.ChangedGroupCount, Report.GroupCount,
Report.UpdatedUsageStateCount,
BoolToStr(Report.RemovedAutomaticState, True)]));
end;
Wyjście zachowuje własny zapis dokumentu PDFium jako przedrostek bajt-w-bajt i dokłada wyłącznie przepisanego właściciela konfiguracji i obiekty OCG niosące słowniki Usage, po czym nową sekcję xref i trailer. Zanim cokolwiek dotrze do twojego celu, wynik jest otwierany ponownie w osobnym TPdf pod ścisłą polityką wczytywania, a metoda zawodzi, gdy to wczytanie się nie powiedzie albo tablica odwołań krzyżowych nie przejdzie walidacji. Przeładowanie plikowe zapisuje do pliku tymczasowego obok celu i podmienia cel dopiero po weryfikacji, więc odrzucona aktualizacja zostawia poprzedni rysunek nietknięty. To to samo podejście zweryfikowanej rewizji przyrostowej, którego używa edytor drzewa nazw i drzewa numerów PDF w PDFium Component. Żeby potwierdzić preset druku, wczytaj wyjście ponownie i wywołaj InspectOptionalContent(Inv, ocuPrint, 1); grupy powinny raportować stany, które podałeś
Czego skonfigurowany zapis odmawia?
Skonfigurowany zapis odmawia każdej zmiany, której dokument zakazuje albo nie umie bezpiecznie unieść, i każda odmowa dzieje się, zanim cel zostanie zapisany. Kontrole biegną w stałej kolejności. Podpisany dokument podnosi EPdfError, zanim cokolwiek innego się wydarzy, chyba że podasz AllowSignedDocument = True. Dokument zaszyfrowany jest odrzucany następny, bo obiekty przyrostowe w jawnym tekście nie mogą unieść aktywnego handlera bezpieczeństwa. Potem sprawdzana jest każda żądana zmiana: numer obiektu, którego nie ma w /OCGs, oblega od razu, a zmiana grupy wymienionej w tablicy /Locked konfiguracji oblega, choć powtórzenie jej bieżącej wartości przechodzi. Na końcu, przy włączonym EnforceRadioGroups, zestaw /RBGroups, który skończyłby z więcej niż jednym widocznym członkiem, jest odrzucany. Każda odmowa poza tą od podpisu zwraca False z powodem w Report.ErrorMessage
Dwie z tych bramek idą dalej, niż wymagaje to specyfikacja w ścisłym sensie. §8.11.4.3 opisuje /Locked jako ochronę przed zmianami przez interfejs użytkownika przeglądarki i opisuje grupy przycisków radiowych jako wyłączające pozostałych członków, gdy jeden zostaje włączony. PDFium Component stosuje obie także do zmian programowych, a przy grupach radiowych odmawia zamiast wybierać członków do wyłączenia. Poproszony o dwóch członków wzajemnie wykluczającego się zestawu, biblioteka nie może wiedzieć, o którego ci chodziło, a zgadywanie przeczy twojemu żądaniu. Wyłącz pozostałych w tej samej tablicy Changes, a zapis przejdzie
Bramka dokumentu podpisanego istnieje, bo dokładana rewizja siedzi na wierzchu pełnego ponownego zapisu PDFium, który jest nową sekwencją bajtów; zakres bajtów, który pokrywa istniejący podpis, nie może być uznany za ocalały. Podawaj AllowSignedDocument = True tylko wtedy, gdy utrata tego podpisu jest do przyjęcia
Trzy skutki uboczne mają znaczenie w zadaniu wsadowym. Przepisanie normalizuje wybraną konfigurację do /BaseState /ON, więc baseline autora /Unchanged albo /OFF staje się jawnymi tablicami z tą samą wynikową widocznością. Zrzucenie /AS usuwa też drukowe sztuczki, jak warstwa znaku wodnego pojawiająca się tylko na papierze; ustaw DisableAutomaticState na False, żeby zachować te reguły i zaakceptować, że mogą nadpisać żądany stan dla swojego zdarzenia. A tablice /ON i /OFF są zapisywane ze stanów policzonych dla Options.Usage, więc preset druku zapisany do konfiguracji 0 zmienia też to, co pokazuje ekran, dla każdej grupy bez słownika Usage. Trzymaj presety druku we wpisie /Configs i zostaw /D ekranowi. Po stronie zgodności PDF/A-2 (ISO 19005-2 klauzula 6.9) i PDF/UA (ISO 14289-1 klauzula 7.10) oba zakazują /AS w słownikach konfiguracji, więc domyślne wyjście czyści jeden finding, który twoja walidacja preflight PDF/A z PDFium Component inaczej by raportowała
Nakładanie zmian warstw wewnątrz przeglądarki Delphi
W przeglądarce kontrola warstw to checklist zbudowany z inwentarza, po czym zapis do pamięci i ponowne wczytanie. Wypełnij checklist z Groups, wyłącz wpisy z ustawioną flagą Locked, traktuj członków dzielących RadioGroupIndex jako wzajemnie wykluczających się, a przy zatwierdzaniu zapisz do TMemoryStream przez przeładowanie strumieniowe. TPdf.LoadDocument oczekuje nieaktywnego komponentu, więc najpierw go wyłącz; buforowane wczytanie kopiuje bajty, co pozwala zwolnić strumień od razu
procedure TFormMain.ApplyLayerChecklist(
const Changes: TPdfOptionalContentStateChanges);
var
Report: TPdfOptionalContentConfigureReport;
Output: TMemoryStream;
KeepPage: Integer;
begin
Output := TMemoryStream.Create;
try
try
if not Pdf.SaveAsOptionalContentConfigured(Output, Changes,
TPdfOptionalContentConfigureOptions.Default, Report) then
begin
ShowMessage('Layer update rejected: ' + Report.ErrorMessage);
Exit;
end;
except
on E: EPdfError do
begin
ShowMessage(E.Message); // podpisany plik: Output wciąż jest pusty
Exit;
end;
end;
KeepPage := Pdf.PageNumber;
Pdf.Active := False;
Pdf.LoadDocument(Output); // Buffered domyślnie True
Pdf.PageNumber := KeepPage;
finally
Output.Free;
end;
end;
Po ponownym wczytaniu PDFium maluje z nowej konfiguracji, więc ekran i następne wywołanie InspectOptionalContent się zgadzają. Okablowanie między TPdf i TPdfView omawia budowa bogatego w funkcje przeglądarki PDF z PDFium VCL w Delphi. Gdy dokument nie ma jeszcze warstw, a potrzebujesz je dodać, tworzenie warstw PDF z OCG i OCMD w Delphi przechodzi przez SaveAsOptionalContentCreated
Cennik, build próbny i pozostałe funkcje dokumentowe komponentu są wypisane na stronie produktu PDFium Component dla Delphi