En opgave lander på dit bord: tag en stak allerede renderede udsagn, slør kontonumrene, og send to sider pr. ark for at spare papir. Begge halvdele af den opgave er content-stream-kirurgi på en PDF, du ikke selv har skabt, så der er ingen venlig sidecanvas at tegne på og ingen fonthåndtering at læne sig op ad. Du redigerer objektgrafen for et indlæst dokument direkte og tilføjer rå tegneoperatorer til en side, som et andet værktøj har lagt ud. HotPDF eksponerer præcis to indgangspunkter til dette, og den farligste af de to er den, der ser harmløs ud
HotPDF er en native VCL PDF-komponent til Delphi og C++Builder. Dens loaded-document API i round-nine tilføjede de første metoder, der opretter helt nyt indhold på en side, du har åbnet fra disk, i stedet for på en side, du selv byggede fra bunden. To af dem er emnet her: RedactLoadedRect, som maler et uigennemsigtigt rektangel over et område, og StitchLoadedPage, som skalerer én side og tegner den oven på en anden. Begge arbejder ved at skrive ISO 32000-1 §8.5 content-stream-operatorer ind i sidens /Contents-stream. At forstå, hvad de operatorer gør, og lige så vigtigt hvad de ikke gør, er forskellen mellem et værktøj, der virker, og et databrud
Tilføj operatorer til en indlæst side
Når du bygger en side med den normale HotPDF-API, ejer komponenten content-streamen og serialiserer dine TextOut- og vektorkald for dig. En indlæst side er anderledes: dens /Contents er et eksisterende stream-objekt, muligvis delt, muligvis en del af et content-array, og du skal splejse ind i det uden at ødelægge det, der allerede er der. Round nine introducerede tre små hjælpefunktioner, der gør det sikkert. NewIndirectStream allokerer en frisk indirekte THPDFStreamObject med en tom buffer og en /Length 0-post; ResolveLoadedStream følger en indirekte reference ned til den underliggende stream; og AppendLoadedStream skriver rå bytes til slutningen af streamen og omskriver /Length, så det gemte objekt forbliver velformet
Mønstret, begge offentlige metoder følger, er det samme. Find sidens /Contents, opløs det til en stream, og hvis der ikke findes en brugbar stream, opret én og tilknyt den. Tilføj derefter operatorerne. Fordi de nye bytes går til slutningen af streamen, garanterer painterens model, at de rendres oven på alt, hvad det oprindelige layout tegnede. Den rækkefølge er hele mekanismen bag redaktionsrektanglet, og det er også grunden til, at det rektangel ikke er, hvad de fleste går ud fra
RedactLoadedRect: et uigennemsigtigt dække, ikke en sletning
RedactLoadedRect tager et nulbaseret sideindeks, fire user-space-koordinater og tre farvekomponenter i intervallet 0–1:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('statement.pdf') > 0 then
begin
// Dæk kontonummerbåndet på side 1 med massiv sort.
// Koordinater er PDF user space: origo nederst til venstre, punkter.
Pdf.RedactLoadedRect(0, 56, 690, 320, 706, 0, 0, 0);
Pdf.SaveLoadedDocument('statement-covered.pdf');
end;
finally
Pdf.Free;
end;
end;
Under motorhjelmen udsender metoden tre operatorer ind i content-streamen: en fyldfarve sat i DeviceRGB (r g b rg), en rektangelsti (x y w h re), og en fyldning (f). Bredden og højden udledes som X2 - X1 og Y2 - Y1, så du angiver to modsatte hjørner og lader metoden beregne udstrækningen. Angiv 0, 0, 0 for farven, og du får en sort bjælke; angiv 1, 1, 1 for en hvid, der matcher en hvid side. Koordinaterne er den indlæste sides eget user space, hvilket betyder, at origo er nederste venstre hjørne, og enhederne er punkter, og det betyder også, at du skal bruge sidens /MediaBox for at placere noget præcist; GetLoadedPageBox med pbMediaBox giver dig det
Læs dette to gange: et udfyldt rektangel dækker indhold visuelt, det fjerner det ikke. Teksten, billedet eller vektorgrafikken under rektanglet er stadig til stede i PDF'en, stadig i objektgrafen, stadig udtrækkelig af enhver, der kopierer siden, kører en tekstudtrækker eller blot sletter dit rektangel fra content-streamen. Dette er visuel maskering, ikke redaktion i juridisk eller sikkerhedsmæssig forstand. Skjuler du reelt følsomme data — kontonumre, journaler, identiteter, noget reguleret — er det at dække det med en sort boks og sende filen et databrud, der bare venter på at blive opdaget. Ægte redaktion kræver at slette de underliggende indholdsobjekter, ikke at male hen over dem
Metodenavnet siger "Redact", og det er en nyttig advarsel om, hvordan resultatet vil blive fejllæst, ikke et løfte om, hvad det sletter. Implementeringen er ærlig om det i sin egen kommentar: den kalder sig selv den "visuelle redaktionsprimitiv" og bemærker, at indholdsfjernende redaktion kræver en content-stream-fortolker, der gennemgår og omskriver de eksisterende operatorer. HotPDFs loaded-document-sti gør ikke det her. Så den sikre regel er snæver: brug RedactLoadedRect til ikke-følsom kosmetisk maskering — skjule et udkast-vandmærke, blanke et område før et skærmbillede, dække et forældet logo på en intern korrektur. I det øjeblik det, der er under boksen, ville betyde noget, hvis det lækkede, er denne metode det forkerte værktøj, og det rigtige svar er at genskabe dokumentet uden dataene eller bruge en reel indholdsfjernelses-pipeline
StitchLoadedPage: skaler, flyt, tegn
N-up-udskydning er det venligere problem, fordi intet skjules, kun omarrangeres. StitchLoadedPage tager et målsideindeks, et kildesideindeks, en X/Y-forskydning og en skaleringsfaktor, og den tegner kildesiden ind på målet ved den position og størrelse:
// Læg side 2 (indeks 1) oven på side 1 (indeks 0),
// skaleret til 70% og skubbet op-højre.
Pdf.StitchLoadedPage(0, 1, 40, 380, 0.7);
// Bekvemmelighed 2-up: kildesiden på højre halvdel af målet.
Pdf.StitchLoadedPageSideBySide(0, 1);
Operatorstrengen, den tilføjer, er en standard transformér-og-mal-sekvens: q for at gemme grafiktilstanden, en cm-matrix, der bærer skaleringen på diagonalen og forskydningen i translationsslotsene, /StitchSrc Do for at kalde et eksternt objekt, og Q for at gendanne tilstanden. Parret q/Q betyder noget: det isolerer transformationen, så den sammensatte side ikke lækker sit koordinatsystem ind i noget, der tilføjes bagefter. Metoden vogter også mod de oplagte fejl — indekser uden for grænserne, et mål lig med kilden, en ikke-positiv skalering (som den klemmer til 1.0) — og afslutter stille frem for at rejse en fejl, så tjek dine input, fordi en tavs no-op ser identisk ud med succes
StitchLoadedPageSideBySide er en tynd bekvemmelighedsfunktion oven på den generelle metode. Den læser målets media-box-bredde, halverer den og kalder StitchLoadedPage med den halve bredde som X-forskydning og en fast skalering på 0.5, hvilket lægger kilden på højre halvdel. Den hard-codede 0.5 antager, at kilde og mål deler en bredde; hvis de ikke gør, vil kilden ikke udfylde sin halvdel pænt, og du vil have brug for den generelle StitchLoadedPage med en skalering, du selv beregner ud fra begge media boxes
Den forenklede XObject-strategi og dens ISO-afvejning
Her er der, hvor implementeringen tager en bevidst genvej, du bør kende, før du stoler på outputtet på tværs af viewere. En korrekt N-up-udskydning pakker kildesidens indhold ind i et Form XObject — et selvstændigt tegnbart objekt, som ISO 32000-1 §8.10.1 siger skal bære /Type /XObject, /Subtype /Form og sin egen /BBox-klippeboks. HotPDFs round-nine-sammensyning bygger ikke den wrapper. I stedet registrerer den kildens page dictionary selv direkte under målets /Resources /XObject med navnet StitchSrc og tegner den derefter med Do. En page dict og et Form XObject deler nok af deres indholdsmodel — begge refererer til en content-stream og et resource-dictionary — at mange readers vil rendre resultatet
Men det er ikke et konformt Form XObject. Det mangler markøren /Subtype /Form og sin egen /BBox, hvilket betyder, at en streng consumer har fuld ret til at ignorere Do eller klippe det anderledes, end du forventer. TechnicalNotes for denne round siger det ligeud: tilgangen "rendres under de fleste readers", men er "ikke et strengt ISO-kompatibelt Form XObject", og fuld overholdelse kræver at syntetisere en rigtig Form XObject-stream som et separat trin. Så behandl stitch-outputtet, som du ville behandle enhver ikke-konform konstruktion: verificér den i de specifikke viewere, dine kunder kører, ikke bare den på din egen maskine, og har du brug for arkiv- eller streng-validator-rene PDF'er, skal du ikke stole på denne sti. Den samme disciplin gælder for alt, du bygger på den indlæste objektgraf, hvilket er grunden til, at et PDF preflight-pas i Delphi fortjener sin plads i udgivelsespipelinen, når du muterer dokumenter programmatisk
Hvor de passer ind, og hvor de ikke gør
Begge metoder er content-stream-værktøjer, så den mentale model er den samme, du bruger til direkte tegning. Har du bygget sider fra bunden med komponenten, vil vektor- og farveoperatorerne bag disse kald se bekendte ud fra HotPDF canvas-tegning i Delphi; forskellen er blot, at du her tilføjer til en stream, en anden har forfattet, i stedet for en, du selv ejer. Hold tre grænser for øje:
- Redaktion er kosmetisk.
RedactLoadedRectmaler hen over indhold og sletter det aldrig. For alt følsomt skal du genskabe kilden eller bruge reel indholdsfjernelse — en sort boks er ikke sikkerhed - Stitch er ikke-konform med vilje. Kildesiden refereres som et pseudo-XObject uden §8.10.1's
/Subtype /Formog/BBox, så bekræft rendering i dine målviewere, og undgå det, hvor streng validering kræves - Koordinater er sidens user space. Origo nederst til venstre, punkter, styret af sidens egen media box. Læs boksen med
GetLoadedPageBox, før du placerer noget, fordi den side, du indlæste, måske ikke har den størrelse, du antog
Brugt inden for de grænser dækker parret en reel workflow: omarrangér sider til print, maskér ikke-fortrolige områder, og skriv resultatet tilbage med SaveLoadedDocument — alt sammen uden en fuld genrendering. Loaded-document-API'et, der inkluderer disse stitch- og maskeringsprimitiver, følger med i HotPDF-Delphi-komponenten til Delphi og C++Builder, sammen med form-field-, annotations- og FDF-metoderne fra samme round