Articol tehnic

Redactare PDF și îmbinare N-up în Delphi cu HotPDF

Primești o cerere clară: ia un set de extrase deja randate, ascunde numerele de cont și livrează două pagini pe foaie ca să economisești hârtie. Ambele jumătăți ale acestei sarcini sunt chirurgie asupra fluxului de conținut pe un PDF pe care nu l-ai creat, așa că nu ai o pânză de pagină prietenoasă pe care să desenezi și nici un manager de fonturi pe care să te bazezi. Editezi direct graful de obiecte al unui document încărcat, adăugând operatori de desen brut pe o pagină pe care alt instrument a așezat-o deja. HotPDF expune exact două puncte de intrare pentru asta, iar cel mai periculos dintre ele este cel care pare inofensiv

HotPDF este o componentă PDF VCL nativă pentru Delphi și C++Builder. API-ul său pentru documente încărcate din runda a noua a adăugat primele metode care creează conținut complet nou pe o pagină pe care ai deschis-o de pe disc, nu pe una construită de la zero. Două dintre ele sunt subiectul aici: RedactLoadedRect, care desenează un dreptunghi opac peste o regiune, și StitchLoadedPage, care scalează o pagină și o desenează pe alta. Ambele funcționează scriind operatori din fluxul de conținut ISO 32000-1 §8.5 în/Contents fluxul paginii. Să înțelegi ce fac acei operatori și, la fel de important, ce nu fac, este diferența dintre un instrument care funcționează și o scurgere de date

Adăugarea de operatori la o pagină încărcată

Când construiești o pagină cu API-ul normal HotPDF, componenta deține fluxul de conținut și serializează pentru tine apelurile către TextOut și către vectori. O pagină încărcată este diferită: /Contents este un obiect stream existent, posibil partajat, posibil parte dintr-un array de conținut, iar tu trebuie să îl intercalezi fără să corupi ce există deja. Runda a noua a introdus trei ajutoare mici care fac asta în siguranță. NewIndirectStream alocă un obiect indirect nou THPDFStreamObject cu un buffer gol și o /Length 0 intrare; ResolveLoadedStream urmărește o referință indirectă până la streamul de bază; iar AppendLoadedStream scrie bytes brute la sfârșitul streamului și rescrie /Length astfel încât obiectul salvat să rămână bine format

Modelul pe care îl urmează ambele metode publice este același. Găsește /Contents paginii, rezolvă-l într-un stream și, dacă nu există un stream utilizabil, creează unul și atașează-l. Apoi adaugă operatorii. Pentru că noii bytes ajung la sfârșitul streamului, modelul pictorului garantează că se randă peste tot ce a desenat aspectul original. Acea ordine este întregul mecanism din spatele dreptunghiului de redactare și este totodată motivul pentru care acel dreptunghi nu este ceea ce cred majoritatea oamenilor

RedactLoadedRect: o acoperire opacă, nu o ștergere

RedactLoadedRect primește un index de pagină de la zero, patru coordonate în spațiul utilizatorului și trei componente de culoare în intervalul 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;

Sub capotă, metoda emite trei operatori în fluxul de conținut: o setare a culorii de umplere în DeviceRGB (r g b rg), o cale de dreptunghi (x y w h re), și o umplere (f). Lățimea și înălțimea sunt derivate ca X2 - X1 și Y2 - Y1, așa că trimiți două colțuri opuse și lași metoda să calculeze extinderea. Trimite 0, 0, 0 pentru culoare și obții o bară neagră; trimite 1, 1, 1 pentru una albă care se potrivește unei pagini albe. Coordonatele sunt propriul spațiu al paginii încărcate, ceea ce înseamnă că originea este colțul din stânga jos și unitățile sunt puncte, iar asta înseamnă și că ai nevoie de /MediaBox ca să plasezi orice cu precizie; GetLoadedPageBox cu pbMediaBox îți oferă asta

Citește asta de două ori: un dreptunghi umplut acoperă vizual conținutul, nu îl elimină. Textul, imaginea sau grafica vectorială de sub dreptunghi sunt încă prezente în PDF, încă în graful de obiecte, încă extractabile de oricine copiază pagina, rulează un extractor de text sau pur și simplu șterge dreptunghiul tău din fluxul de conținut. Aceasta este mascare vizuală, nu redactare în sens juridic sau de securitate. Dacă ascunzi date cu adevărat sensibile: numere de cont, dosare medicale, identități, orice este reglementat, acoperirea lor cu o cutie neagră și trimiterea fișierului înseamnă o scurgere de date care așteaptă să fie descoperită. Redactarea adevărată cere ștergerea obiectelor de conținut de bază, nu pictarea peste ele

Numele metodei spune "Redact", iar acesta este un avertisment util despre cum va fi interpretat greșit rezultatul, nu o promisiune despre ce șterge. Implementarea este sinceră în această privință chiar în comentariul ei: se numește "visual redaction primitive" și notează că redactarea care elimină conținutul are nevoie de un interpret al content-stream-ului care parcurge și rescrie operatorii existenți. Calea pentru documentele încărcate din HotPDF nu face asta aici. Așadar, regula sigură este îngustă: folosește RedactLoadedRect pentru mascarea cosmetică a elementelor nesensibile - ascunderea unui watermark de schiță, golirea unei regiuni înainte de o captură de ecran, acoperirea unui logo depășit într-un proof intern. În clipa în care ceea ce se află sub casetă ar conta dacă s-ar scurge, această metodă este instrumentul greșit, iar răspunsul corect este să regenerezi documentul fără datele respective sau să folosești un flux real de eliminare a conținutului

StitchLoadedPage: scalează, translatează, desenează

Impunerea N-up este problema mai prietenoasă, pentru că nimic nu este ascuns, ci doar reorganizat. StitchLoadedPage primește un index de pagină țintă, un index de pagină sursă, un offset X/Y și un factor de scalare și desenează pagina sursă pe țintă în acea poziție și la acea dimensiune:

// 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);

Șirul de operatori pe care îl adaugă este o secvență standard de transformare și desenare: q pentru a salva starea grafică, un cm matrix care poartă scala pe diagonală și offsetul în sloturile de translație, /StitchSrc Do pentru a invoca un obiect extern, și Q pentru a restaura starea. Perechea q/Q contează: izolează transformarea, astfel încât pagina asamblată să nu-și reverse sistemul de coordonate în nimic adăugat după aceea. Metoda mai protejează și greșelile evidente - indecși în afara intervalului, o țintă egală cu sursa, o scală nepozitivă (pe care o limitează la 1.0) - și iese liniștit în loc să arunce, așa că verifică-ți intrările, deoarece un no-op tăcut arată identic cu succesul

StitchLoadedPageSideBySide este o comoditate subțire peste metoda generală. Citește lățimea media box-ului țintei, o înjumătățește și apelează StitchLoadedPage cu acea jumătate de lățime ca offset X și cu o scală fixă de 0.5, plasând sursa pe jumătatea dreaptă. Acea valoare hardcodată de 0.5 presupune că sursa și ținta au aceeași lățime; dacă nu, sursa nu va umple curat jumătatea ei și vei vrea metoda generală StitchLoadedPage cu o scală pe care o calculezi singur din ambele media box-uri

Strategia simplificată cu XObject și compromisurile ISO

Aici implementarea face o scurtătură deliberată pe care trebuie să o știi înainte să ai încredere în rezultat pe mai multe viewer-e. O impunere N-up corectă încapsulează conținutul paginii sursă într-un Form XObject - un obiect desenabil autonom pe care ISO 32000-1 §8.10.1 spune că trebuie să conțină /Type /XObject, /Subtype /Form, și propriul /BBox de clipping. HotPDF's round-nine stitch nu construiește acest wrapper. În schimb, înregistrează sursa dicționarul de pagină direct sub /Resources /XObject cu numele StitchSrc, apoi îl desenează cu Do. Un dicționar de pagină și un Form XObject împart suficient din modelul lor de conținut - ambele fac referire la un content stream și la un resource dictionary - încât mulți cititori vor reda rezultatul

Dar nu este un Form XObject conform. Îi lipsesc markerul /Subtype /Form și propriul /BBox, ceea ce înseamnă că un consumator strict are tot dreptul să ignore Do sau să îl decupeze altfel decât te aștepți. TechnicalNotes pentru această rundă spun asta clar: abordarea "renders under most readers" dar este "not a strictly ISO-compliant Form XObject", iar conformitatea deplină cere sintetizarea unui Form XObject real ca pas separat. Așadar tratează ieșirea stitch la fel cum ai trata orice construcție neconformă: verific-o în cititoarele specifice pe care le folosesc clienții tăi, nu doar în cel de pe mașina ta, iar dacă ai nevoie de PDF-uri de arhivă sau care trec strict-validatorii curat, nu te baza pe această cale. Aceeași disciplină se aplică la orice construiești pe graful de obiecte încărcat, motiv pentru care un PDF preflight pass in Delphi își merită locul în pipeline-ul de release ori de câte ori modifici documente programatic

Unde se potrivesc aceste metode și unde nu

Ambele metode sunt instrumente de content-stream, așa că modelul mental este același pe care îl folosești pentru desenarea directă. Dacă ai construit pagini de la zero cu componenta, operatorii vectoriali și de culoare din spatele acestor apeluri îți vor părea familiari din HotPDF canvas drawing in Delphi; diferența este doar că aici adaugi la un stream pe care altcineva l-a scris, nu la unul al tău. Ține minte trei limite:

  • Redactarea este cosmetică. RedactLoadedRect pictează peste conținut și nu îl șterge niciodată. Pentru orice lucru sensibil, regenerează sursa sau folosește eliminarea reală a conținutului - o cutie neagră nu este securitate
  • Stitch este neconform prin design. Pagina sursă este referențiată ca un pseudo-XObject fără §8.10.1 /Subtype /Form și /BBox, așa că verifică randarea în vizualizatoarele țintă și evită această abordare acolo unde este necesară validarea strictă
  • Coordonatele sunt spațiul utilizator al paginii. Origine în stânga jos, în puncte, ghidate de caseta media proprie a paginii. Citește caseta cu GetLoadedPageBox înainte să plasezi orice, deoarece pagina încărcată s-ar putea să nu aibă dimensiunea pe care ai presupus-o

Folosită în limitele acestea, perechea acoperă un flux de lucru real: rearanjează pagini pentru imprimare, maschează zone neconfidențiale și scrie rezultatul înapoi cu SaveLoadedDocument , fără o rerandare completă. API-ul pentru documente încărcate care include aceste primitive de stitch și mask vine cu HotPDF Component pentru Delphi și C++Builder, alături de metodele pentru câmpuri de formular, adnotări și FDF din aceeași rundă