Tehnički članak

Redigiranje PDF-a i N-up slaganje u Delphiju uz HotPDF

Na vaš stol stiže zahtjev: uzeti hrpu već izrađenih izvoda, zacrniti brojeve računa i poslati dvije stranice po listu da se uštedi papir. Oba dijela tog posla su kirurgija nad sadržajnim tokom PDF-a koji niste vi stvorili, pa nemate prijateljsku stranicu platna za crtanje i nemate upravitelj fontova na kojeg biste se oslonili. Izravno uređujete graf objekata učitanog dokumenta i dodajete sirove operatore crtanja na stranicu koju je neki drugi alat već rasporedio. HotPDF za to izlaže točno dvije ulazne točke, a opasnija od njih je ona koja izgleda bezazleno

HotPDF je izvorna VCL PDF komponenta za Delphi i C++Builder. Njezin API za učitane dokumente u devetom izdanju dodao je prve metode koje create novi sadržaj na stranici koju ste otvorili s diska, a ne na onoj koju ste izgradili od nule. Dvije od njih su tema ovdje: RedactLoadedRect, koja slika neprozirni pravokutnik preko područja, i StitchLoadedPage, koja skalira jednu stranicu i crta je na drugu. Obje rade upisivanjem operatora sadržajnog toka ISO 32000-1 §8.5 u stranicin /Contents tok. Razumijevanje što ti operatori rade, a jednako važno i što ne rade, razlika je između alata koji radi i povrede podataka

Dodavanje operatora na učitanu stranicu

Kada stranicu gradite normalnim HotPDF API-jem, komponenta posjeduje sadržajni tok i serializira vaše TextOut tekstualne i vektorske pozive za vas. Učitana stranica je drukčija: njezin /Contents je postojeći stream objekt, možda dijeljen, možda dio niza sadržaja, i morate ga umetnuti bez kvarenja onoga što je već ondje. Deveto izdanje uvelo je tri mala pomoćnika koji to čine sigurnim. NewIndirectStream dodjeljuje svježi neizravni THPDFStreamObject s praznim međuspremnikom i zapisom /Length 0; ResolveLoadedStream prati neizravnu referencu do temeljnog toka; i AppendLoadedStream upisuje sirove bajtove na kraj toka i ponovno piše /Length tako da spremljeni objekt ostane ispravno oblikovan

Obrazac koji slijede obje javne metode isti je. Pronađite stranicin /Contents, razriješite ga u tok i ako nema upotrebljivog toka, stvorite jedan i priključite ga. Zatim dodajte operatore. Budući da novi bajtovi idu na end toka, painterov model jamči da se prikazuju iznad svega što je izvorni raspored nacrtao. Taj poredak je cijeli mehanizam iza pravokutnika za redigiranje, i također je razlog zašto taj pravokutnik nije ono što većina ljudi pretpostavlja

RedactLoadedRect: neprozirni pokrov, a ne brisanje

RedactLoadedRectRedactLoadedRect uzima indeks stranice od nule, četiri koordinate u korisničkom prostoru i tri komponente boje u rasponu 0–1:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('statement.pdf') > 0 then
    begin
      // Cover the account-number band on page 1 with solid black.
      // Coordinates are PDF user space: origin bottom-left, points.
      Pdf.RedactLoadedRect(0, 56, 690, 320, 706, 0, 0, 0);
      Pdf.SaveLoadedDocument('statement-covered.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Ispod haube metoda ispisuje tri operatora u sadržajni tok: postavku boje ispune u DeviceRGB (r g b rg), putanju pravokutnika (x y w h re), i ispunu (f). Širina i visina izvode se kao X2 - X1 i Y2 - Y1, pa prosljeđujete dva suprotna kuta i puštate metodu da izračuna opseg. Proslijedite 0, 0, 0 za boju i dobijete crnu traku; proslijedite 1, 1, 1 za bijelu koja odgovara bijeloj stranici. Koordinate su vlastiti korisnički prostor učitane stranice, što znači da je ishodište dolje lijevo, a jedinice su točke, i to također znači da vam za točno postavljanje treba stranicin /MediaBox da biste nešto precizno smjestili; GetLoadedPageBox s pbMediaBox daje vam to

Pročitajte ovo dvaput: ispunjeni pravokutnik vizualno pokriva sadržaj, ali ga ne uklanja. Tekst, slika ili vektorska grafika ispod pravokutnika i dalje su prisutni u PDF-u, i dalje u grafu objekata, i dalje ih može izdvojiti svatko tko kopira stranicu, pokrene extractor teksta ili jednostavno izbriše vaš pravokutnik iz sadržajnog toka. To je vizualno maskiranje, a ne redigiranje u pravnom ili sigurnosnom smislu. Ako skrivate doista osjetljive podatke — brojeve računa, medicinsku dokumentaciju, identitete, bilo što regulirano — prekrivanje crnom kutijom i slanje datoteke je curenje podataka koje samo čeka da bude otkriveno. Pravo redigiranje zahtijeva brisanje temeljnih objekata sadržaja, a ne bojanje preko njih

Naziv metode kaže "Redact", i to je korisno upozorenje o tome kako će rezultat biti pogrešno shvaćen, a ne obećanje o onome što briše. Implementacija je o tome iskrena u vlastitom komentaru: naziva samu sebe "vizualnom redakcijskom primitivom" i napominje da redigiranje koje uklanja sadržaj treba interpreter sadržajnog toka koji prolazi i prepisuje postojeće operatore. Put učitanih dokumenata HotPDF-a to ovdje ne radi. Zato je sigurno pravilo usko: koristite RedactLoadedRect za neosjetljivo kozmetičko maskiranje - skrivanje nacrta vodenog žiga, pražnjenje područja prije snimke zaslona, prekrivanje zastarjelog logotipa na internom probnom otisku. Čim bi ono ispod kutije bilo važno kad bi procurilo, ova metoda je pogrešan alat, a pravi odgovor je ponovno generirati dokument bez podataka ili koristiti pravi cjevovod za uklanjanje sadržaja

StitchLoadedPage: skaliraj, premjesti, nacrtaj

Slaganje N-up je blaži problem jer ništa nije skriveno, samo preuređeno. StitchLoadedPage uzima indeks ciljne stranice, indeks izvorišne stranice, X/Y pomak i faktor skaliranja, i crta izvornu stranicu na ciljanu na toj poziciji i veličini:

// Overlay page 2 (index 1) onto page 1 (index 0),
// scaled to 70% and nudged up-right.
Pdf.StitchLoadedPage(0, 1, 40, 380, 0.7);

// Convenience 2-up: source page on the right half of the target.
Pdf.StitchLoadedPageSideBySide(0, 1);

Niz operatora koji dodaje standardan je slijed transformacije i crtanja: q za spremanje grafičkog stanja, cm matrica koja nosi skalu na dijagonali i pomak u translacijskim utorima, /StitchSrc Do za pozivanje vanjskog objekta, i Q za vraćanje stanja. q/Q par je važan: izolira transformaciju tako da se složena stranica ne prelije u bilo što što se dodaje nakon nje. Metoda također hvata očite pogreške - indeksi izvan raspona, cilj jednak izvoru, neskaliranje ili negativnu skalu (koju ograničava na 1.0) - i izlazi tiho umjesto da baca iznimku, pa provjerite ulaz jer tihi no-op izgleda isto kao uspjeh

StitchLoadedPageSideBySide je tanka pogodnost nad općom metodom. Čita širinu ciljane media box, prepolovi je i zove StitchLoadedPage s tom poluširinom kao X pomakom i fiksnom skalom od 0.5, stavljajući izvor na desnu polovicu. Ta tvrdo zadana vrijednost 0.5 pretpostavlja da izvor i cilj dijele širinu; ako ne, izvor neće uredno ispuniti svoju polovicu i željet ćete opću StitchLoadedPage s skalom koju sami izračunate iz obje media boxe

Pojednostavljena strategija XObjecta i njezin ISO kompromis

Ovdje implementacija radi namjernu prečicu koju morate znati prije nego što vjerujete izlazu kroz različite preglednike. Ispravno slaganje N-up omota sadržaj izvorišne stranice u Form XObject - samostalan drawable objekt koji ISO 32000-1 §8.10.1 kaže da mora nositi /Type /XObject, /Subtype /Form i vlastiti /BBoxUmjesto toga registrira sam rječnik stranice izravno ispod ciljnog /Resources /XObject pod resursima /XObject s imenom StitchSrc, a zatim ga crta s Do. Stranica i Form XObject dijele dovoljno modela sadržaja - oba referiraju sadržajni tok i rječnik resursa - da će mnogi čitači prikazati rezultat

Ali to nije usklađen Form XObject. Nedostaje mu /Subtype /Form oznaka i vlastiti /BBox, što znači da striktni potrošač ima pravo ignorirati Do ili ga odrezati drugačije nego što očekujete. TechnicalNotes za ovaj krug to kažu jasno: pristup "renders under most readers" ali je "not a strictly ISO-compliant Form XObject", a potpuna usklađenost zahtijeva sintetiziranje pravog Form XObject toka kao zaseban korak. Zato stitch izlaz tretirajte kao bilo koji neusklađen konstrukt: provjerite ga u konkretnim preglednicima koje vaši korisnici doista koriste, a ne samo u onome na vašem računalu, i ako vam trebaju arhivski ili validatorima čisti PDF-ovi, nemojte se oslanjati na ovaj put. Ista disciplina vrijedi za sve što gradite na učitanom grafu objekata, zbog čega PDF preflight prolaz u Delphiju zaslužuje svoje mjesto u release pipelineu svaki put kad dokumente mijenjate programatski

Gdje ovo pripada, a gdje ne

Obje metode su alati sadržajnog toka, pa je mentalni model isti onaj koji koristite za izravno crtanje. Ako ste gradili stranice od nule s komponentom, vektorski i kolor operatori iza ovih poziva bit će vam poznati iz HotPDF canvas crtanja u Delphiju; razlika je samo u tome što ovdje dodajete u tok koji je netko drugi autorirao, a ne u onaj koji ste vi vlasnik. Držite tri granice na umu:

  • Redigiranje je kozmetičko. RedactLoadedRect prekriva sadržaj i nikad ga ne briše. Za bilo što osjetljivo, ponovno generirajte izvor ili koristite pravo uklanjanje sadržaja - crna kutija nije sigurnost
  • Stitch nije usklađen po dizajnu. Izvorna stranica referencira se kao pseudo-XObject bez §8.10.1 /Subtype /Form i /BBox, pa potvrđujte renderiranje u svojim ciljanim preglednicima i izbjegavajte ga ondje gdje je potrebna stroga provjera
  • Koordinate su korisnički prostor stranice. Ishodište je dolje lijevo, jedinice su točke, a sve upravlja vlastiti media box stranice. Pročitajte box pomoću GetLoadedPageBox prije nego što išta postavite, jer stranica koju ste učitali možda nije veličine koju ste pretpostavili

Kad se koriste unutar tih granica, ovaj par pokriva stvarni workflow: prerasporedite stranice za ispis, maskirate neosjetljiva područja i vratite rezultat natrag s SaveLoadedDocument - sve bez potpunog ponovnog renderiranja. API za učitane dokumente koji uključuje te stitch i mask primitive dolazi s HotPDF Component za Delphi i C++Builder, uz metode za polja obrazaca, anotacije i FDF iz istog izdanja