Techninis straipsnis

PDF pasirenkamų turinio sluoksnių perjungimas PDFium

PDFium Component valdo PDF pasirenkamo turinio sluoksnius (OCG) Delphi per du TPdf metodus: InspectOptionalContent išvardija kiekvieną sluoksnį kartu su matomumu, kurį PDFium iš tikrųjų atvaizduos, o SaveAsOptionalContentConfigured rašo patikrintą kopiją, kurioje jūsų pasirinkti sluoksniai įjungti arba išjungti. Antrasis metodas dar neutralizuoja Usage ir /AS taisykles, kurios kitu atveju tyliai panaikintų jūsų redagavimą. Abu dirba su dokumentu, jau atidarytu TPdf, tad nėra antro analizatoriaus, kurį reikėtų laikyti sinchronyje su tuo, ką rodo žiūryklė

Užklausa paprastai atkeliauja iš CAD ar GIS dirbtuvių: brėžinių rinkinys keliauja su matmenimis, pastabomis ir pavadinimų bloku atskiruose sluoksniuose, o klientas nori kopijos su paslėptais matmenimis, dar jai nukeliavus pas tiekėją. PDFium pasirenkamą turinį atvaizduoja teisingai, bet jo viešasis ABI neturi funkcijos išvardyti OCG, parinkti konfigūraciją ar perversti sluoksnio būseną. Tad nusileidžiate iki objektų lygio, redaguojate /OCProperties, išsaugote, perkraunate — ir sluoksnis tebečia. Priežastis yra PDFium matomumo logika, ir verta ją suprasti, dar nelietus jokių baitų

Kodėl /ON ir /OFF redagavimas nekeičia to, ką PDFium atvaizduoja?

Konfigūracijos žodyno /ON ir /OFF masyvų redagavimo neužtenka, nes PDFium leidžia aiškiai būsenai OCG paties /Usage žodyne nugalėti tuos masyvus, o /AS automatinės būsenos taisyklė gali tada perrašyti abu. ISO 32000-1 §8.11.4 konfigūracijas ir usage žodynus aprašo kaip atskirus mechanizmus; PDFium atvaizduoklis juos susuka į vieną sprendimą, o InspectOptionalContent jį atkuria šia tvarka:

  • Pradedama nuo konfigūracijos /BaseState, kur /ON ir /Unchanged abu laikomi matomais ir tik /OFF slepia
  • Taikomas konfigūracijos /ON masyvas, tada jo /OFF masyvas, tad abiejuose įrašyta grupė galiausiai paslepiama
  • Taikoma grupės aiški Usage būsena prašomam usage, pavyzdžiui /Usage << /View << /ViewState /OFF >> >>, kuri perrašo visa, kas aukščiau
  • Grupė, kurios /Intent neturi nei /View, nei /All, laikoma matoma, nes ji nedalyvauja view-intent matomume
  • Galiausiai paleidžiamas pasirinktos konfigūracijos /AS masyvas, kurio įrašai atitinkamam įvykiui nustato savo įrašomų grupių būseną
Penkių žingsnių matomumo sprendimas, kurį PDFium Component atkuria kiekvienai PDF pasirenkamo turinio grupei Delphi: BaseState nustato pradžią, konfigūracijos ON ir OFF masyvai taikomi tvarka, aiškus Usage ViewState arba PrintState įrašas perrašo abu, Intent nedalyvavimas skaitomas kaip matomas, o AS masyvas paleidžiamas paskutinis
ON ir OFF masyvų redagavimo neužtenka, nes PDFium BaseState, abu masyvus, grupės Usage būseną ir galiausiai AS automatinės būsenos taisykles susuka į vieną verdiktą, kurį InspectOptionalContent atkuria žingsnis po žingsnio

Trečiasis žingsnis — tas, kuris degina žmones. Maketavimo įrankio išsaugotas failas dažnai neša /ViewState /ON kiekvienam OCG, ir PDFium tada ignoruoja jūsų kruopščiai redaguotą /OFF masyvą: įrašymas pavyksta, failas švariai atsidaro, o sluoksnis tebepiešiamas. Print ir Export atveju OcExplicitUsageState pirmiausia skaito PrintState arba ExportState ir nusileidžia į ViewState, kai konkretaus įrašo nėra, tad vienas ViewState /ON prisega sluoksnį ir spausdinimui. Pažymėtas turinys, rodantis į OCMD (§8.11.2.2), tada išsprendžiamas pagal šiuos grupės rezultatus — per /P politiką arba, jei yra, /VE matomumo išraišką

Kaip išvardyti sluoksnius, kuriuos PDFium iš tikrųjų rodys?

TPdf.InspectOptionalContent grąžina TPdfOptionalContentInventory, kurio Groups masyvas neša kiekvieno OCG objekto numerį, pavadinimą, intentus, tris Usage būsenas, kalbą, zoom diapazoną, Locked žymę, radiogrupės indeksą ir apskaičiuotąjį EffectiveVisible. Metodas pirmiausia liepia PDFium išsaugoti esamą atmintyje esantį dokumentą, išskleidžia objektų srautus ir nuskanuoja rezultatą, tad sesijoje anksčiau atlikti redagavimai atsispindi. Konfigūracijos indeksas 0 visada yra numatytasis /D žodynas, o /Configs įrašai eina nuo indekso 1; numatytasis -1 argumentas parenka indeksą 0. Dokumentas be /OCProperties priverčia metodą grąžinti False su priežastimi ErrorMessage, užuot kėlus išimtį

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // Usage pagal nutylėjimą ocuView; -1 parenka konfigūraciją 0, /D žodyną
  if not Pdf.InspectOptionalContent(Inv) then
  begin
    Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
    Exit;
  end;
  Memo1.Lines.Add(Format('Configuration %d: %s',
    [Inv.SelectedConfigurationIndex,
     string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
  for G in Inv.Groups do
    Memo1.Lines.Add(Format('obj %d  %s  visible=%s  locked=%s  radio=%d',
      [G.ObjectNumber, string(G.Name),
       BoolToStr(G.EffectiveVisible, True),
       BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;

Memberships masyvas praneša kiekvieną OCMD su jo Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), žaliuoju VisibilityExpression tekstu ir savuoju EffectiveVisible. Kelios kraštinės taisyklės sąmoningos. /P pagal nutylėjimą /AnyOn, o OCMD be grupių skaitomas matomu. Nuoroda į objekto numerį, kuris nėra žinomas OCG, laikoma matoma, užuot sugriovusi visą išraišką. /VE vertinimas sustoja prie 32 lizdavimo gylio ir bet ką giliau laiko paslėpta, tad priešiška ar į save rodanti išraiška negali apžiūros paversti steko perpildymu

Naujos sluoksnio būsenos rašymas su SaveAsOptionalContentConfigured

TPdf.SaveAsOptionalContentConfigured ima TPdfOptionalContentStateChange įrašų masyvą (grupės objekto numeris plius Visible) ir rašo dokumentą, kuriame pasirinkta konfigūracija duoda būtent tą būseną. Pasirinkta konfigūracija gauna /BaseState /ON plius pilnus /ON ir /OFF masyvus, dengiančius kiekvieną grupę, o kiekvienas OCG, jau turintis Usage žodyną, gauna aiškią ViewState (arba PrintState / ExportState, pagal Options.Usage), atitinkančią jo naują būseną. Su TPdfOptionalContentConfigureOptions.Default pasirinktos konfigūracijos /AS raktas pašalinamas, tad atidarymo, spausdinimo ar eksporto įvykis negali sluoksnių grąžinti atgal

procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
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;

  // Konfigūracija 0, ocuView, DisableAutomaticState ir EnforceRadioGroups True
  Options := TPdfOptionalContentConfigureOptions.Default;

  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;

Rašymo kelias PDFium patį išsaugotą išvestį laiko baitas į baitą priešdėliu ir prijungia tik perrašytą konfigūracijos savininką ir OCG objektus, nešančius Usage žodynus, po jų — naują xref sekciją ir trailer. Dar nė vienas baitas nepasiekus jūsų paskirties vietos, rezultatas atveriamas atskirame TPdf pagal griežtą įkėlimo politiką, ir metodas žlunga, jei kryžminių nuorodų lentelė nepraena patikros. Failo perkrova žengia žingsniu toliau: rašo į laikinąjį failą šalia tikslo ir pakeičia tikslą tik sėkmingai patikrinus, tad atmestas atnaujinimas niekada nepalieka pusiau parašyto brėžinio. Tai tas pats patikrintų inkrementinių revizijų būdas, kurį naudoja PDF vardų ir numerių medžių redaktorius PDFium Component

Kaip SaveAsOptionalContentConfigured PDFium Component rašo Delphi PDF su perjungtais sluoksniais: būsenų pokyčiai ir parinktys įeina vidun, pasirinkta konfigūracija perrašoma su pilniais ON ir OFF masyvais ir Usage būsenomis, prijungiama patikrinta inkrementinė revizija, ir griežtas pakartotinis atidarymas turi praeiti patikrą, dar nesurašius nieko
Konfigūruotas įrašymas PDFium patį pakartotiną išsaugojimą laiko baitų priešdėliu, prijungia perrašytą konfigūracijos savininką plius naują xref sekciją ir prieš lietiant paskirtį rezultatą atveria atskirame TPdf

Ką konfigūruotas įrašymas atsisako daryti?

Konfigūruotas įrašymas atsisako bet kokio pakeitimo, kurio pats dokumentas draudžia ar negali saugiai atvaizduoti, ir kiekvienas atsisakymas nutinka dar nelietus paskirties vietos. Objekto numeris, nesantis /OCGs, žlunga iškart. Grupės, įrašytos konfigūracijos /Locked masyve, keitimas žlunga, nors jos dabartinės reikšmės pakartojimas leidžiamas. Su įjungtu EnforceRadioGroups bet kokia /RBGroups aibė, kuri galiausiai turėtų daugiau nei vieną matomą narį, atmetama, užuot tyliai išjungus kitus. Užšifruoti dokumentai atmetami, nes atvirųjų duomenų inkrementiniai objektai negali nešti aktyvaus saugumo apdorotojo. Pasirašyti dokumentai kelia EPdfError, nebent perduodate AllowSignedDocument = True, nes tai, ką rodo puslapis, pakeitus gali sulaužyti parašo aprėptį ar sertifikavimo politiką

Atsisakymo vartai, kuriuos SaveAsOptionalContentConfigured taiko PDFium Component prieš rašydamas konfigūruotą Delphi PDF: objekto numeris už OCG ribų žlunga, užrakintos grupės žlunga, RBGroups aibės su daugiau nei vienu matomu nariu atmetamos, užšifruoti dokumentai negali nešti atvirųjų inkrementinių objektų, o pasirašyti failai reikalauja AllowSignedDocument
Kiekvienas atsisakymas nutinka dar nelietus paskirties vietos, o nesėkmės priežastis atsiduria Report.ErrorMessage, užuot palikus pusiau parašytą brėžinį
function TFormMain.SavePrintPreset(Target: TStream;
  const Changes: TPdfOptionalContentStateChanges): Boolean;
var
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  Options := TPdfOptionalContentConfigureOptions.Default;
  Options.Usage := ocuPrint;          // rašo /Print << /PrintState ... >>
  Options.ConfigurationIndex := 1;    // pirmasis /Configs įrašas, ne /D
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // AllowSignedDocument lieka False
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // pasirašytas failas: į Target nerašoma nieko
      Result := False;
    end;
  end;
end;

Susipažinkite su kompromisais, dar prieš tai prijungdami prie paketinės užduoties. Pridėta revizija stovi ant PDFium pilno pakartotino išsaugojimo, o ne jūsų originalių failo baitų, ir būtent todėl pasirašytai įvestiai reikia aiškaus pritarimo. Perrašymas taip pat suvienodina pasirinktą konfigūraciją iki /BaseState /ON, tad autoriaus /Unchanged arba /OFF bazinė linija pakeičiama aiškiais masyvais su ta pačia gautina matomumu. /AS išmetimas pašalina tik spausdinimui skirtus triukus, tokius kaip vandens ženklelio sluoksnis, atsirandantis tik popieriuje; nustatykite DisableAutomaticState į False, norėdami tuos taisykles palikti, sutikdami, kad jos tam įvykiui gali perrašyti jūsų prašomą būseną. Iš gerosios pusės, PDF/A-2 (ISO 19005-2 6.9 punktas) ir PDF/UA (ISO 14289-1 7.10 punktas) abu draudžia /AS konfigūracijos žodynuose, tad numatytasis išvesties pašalina vieną problemą, kurią jūsų PDF/A preflight patikra su PDFium Component kitu atveju praneštų

Kur sluoksnių valdymas telpa Delphi PDF žiūryklėje

Žiūryklėje sluoksnių valdymas yra varnelėmis valdomas sąrašas, varomas inventoriaus plius išsaugoto rezultato perkrovimo. Užpildykite sąrašą iš Groups, išjunkite Locked įrašus, laikykite tuos pačius RadioGroupIndex dalijančius narius vienas kitam išimamais ir taikydami rašykite į TMemoryStream, o tą srautą vėl įkelkite į TPdf, kad vaizdas nupieštų naują būseną. Sujungimą tarp TPdf ir TPdfView dengia funkcijomis turtingos PDF žiūryklės su PDFium VCL Delphi statymas. Licencijavimas, bandomieji parsisiuntimai ir likusi funkcijų dalis — PDFium Component for Delphi produkto puslapyje