Er komt een verzoek op je bureau: neem een batch al gerenderde overzichten, maak de rekeningnummers zwart en lever twee pagina's per vel aan om papier te besparen. Beide helften van die taak zijn content-streamchirurgie op een PDF die jij niet hebt gemaakt, dus er is geen vriendelijk page canvas om op te tekenen en geen fontmanager om op terug te vallen. Je bewerkt rechtstreeks de objectgraph van een geladen document en voegt ruwe tekenoperators toe aan een pagina die een andere tool heeft opgebouwd. HotPDF biedt daar precies twee ingangen voor, en de gevaarlijkste van de twee is de ingang die er onschuldig uitziet
HotPDF is een native VCL PDF-component voor Delphi en C++Builder. De loaded-document API uit round negen voegde de eerste methoden toe die nieuwe content maken op een pagina die je van schijf hebt geopend in plaats van op een pagina die je vanaf nul hebt gebouwd. Twee ervan staan hier centraal: RedactLoadedRect, dat een ondoorzichtige rechthoek over een gebied tekent, en StitchLoadedPage, dat een pagina schaalt en op een andere pagina tekent. Beide werken door ISO 32000-1 §8.5 content-streamoperators in de /Contents-stream van de pagina te schrijven. Begrijpen wat die operators doen, en net zo belangrijk wat ze niet doen, is het verschil tussen een werkende tool en een datalek
Operators aan een geladen pagina toevoegen
Wanneer je een pagina met de normale HotPDF-API bouwt, beheert de component de content stream en serialiseert hij je TextOut- en vectoraanroepen voor je. Een geladen pagina is anders: de /Contents is een bestaande stream, mogelijk gedeeld, mogelijk onderdeel van een contentarray, en je moet er zonder corruptie tussen schuiven. Round negen introduceerde drie kleine helpers die dat veilig maken. NewIndirectStream wijst een verse indirecte THPDFStreamObject toe met een lege buffer en een /Length 0-entry; ResolveLoadedStream volgt een indirecte referentie naar de onderliggende stream; en AppendLoadedStream schrijft ruwe bytes aan het einde van de stream en herschrijft /Length zodat het opgeslagen object geldig blijft
Het patroon dat beide openbare methoden volgen is hetzelfde. Zoek de /Contents van de pagina, los die op naar een stream en als er geen bruikbare stream is, maak er dan een aan en koppel die. Voeg daarna de operators toe. Omdat de nieuwe bytes aan het einde van de stream komen, garandeert het painter's model dat ze bovenop alles renderen wat de oorspronkelijke layout al tekende. Die volgorde is het hele mechanisme achter de redactie-rechthoek, en ook de reden dat die rechthoek niet is wat de meeste mensen denken
RedactLoadedRect: een ondoorzichtige afdekking, geen verwijdering
RedactLoadedRect neemt een zero-based pagina-index, vier user-space-coördinaten en drie kleurcomponenten in het bereik 0-1:
Onder de motorkap schrijft de methode drie operators in de content stream: een fill-color in DeviceRGB (r g b rg), een rechthoekpad (x y w h re) en een fill (f). De breedte en hoogte worden afgeleid als X2 - X1 en Y2 - Y1, dus je geeft twee tegenoverliggende hoeken door en laat de methode de uitgestrektheid berekenen. Geef 0, 0, 0 voor de kleur en je krijgt een zwarte balk; geef 1, 1, 1 voor een witte die past bij een witte pagina. De coördinaten zijn de eigen user space van de geladen pagina, wat betekent dat de oorsprong linksonder ligt en de eenheden punten zijn, en dat je voor precieze plaatsing de /MediaBox van de pagina nodig hebt; GetLoadedPageBox met pbMediaBox geeft je die
Lees dit twee keer: een gevulde rechthoek dekt content visueel af, maar verwijdert hem niet. De tekst, afbeelding of vectorkunst onder de rechthoek staat nog steeds in de PDF, zit nog steeds in de objectgraph en is nog steeds uit te lezen door iedereen die de pagina kopieert, een tekstextractor draait of simpelweg jouw rechthoek uit de content stream verwijdert. Dit is visuele masking, geen redactie in juridische of beveiligingszin. Als je echt gevoelige data verbergt, rekeningnummers, medische dossiers, identiteiten, alles wat gereguleerd is, dan is het afdekken met een zwarte balk en het bestand versturen een datalek dat wacht om ontdekt te worden. Echte redactie vereist dat de onderliggende contentobjects worden verwijderd, niet dat je eroverheen schildert
De naam van de methode zegt "Redact", en dat is een nuttige waarschuwing over hoe het resultaat zal worden geïnterpreteerd, geen belofte over wat het verwijdert. De implementatie is daar in een eigen commentaar eerlijk over: hij noemt zichzelf de "visual redaction primitive" en merkt op dat redactie waarbij content echt wordt verwijderd een content-streaminterpreter nodig heeft die de bestaande operators doorloopt en herschrijft. Het loaded-documentpad van HotPDF doet dat hier niet. De veilige regel is dus smal: gebruik RedactLoadedRect voor niet-gevoelige cosmetische masking, bijvoorbeeld een conceptwatermerk verbergen, een gebied afdekken voor een screenshot of een verouderd logo op een intern proefexemplaar maskeren. Op het moment dat iets onder de balk relevant zou zijn als het lekt, is deze methode het verkeerde gereedschap, en is het juiste antwoord om het document zonder die data opnieuw te genereren of een echt content-removal-pipeline te gebruiken
StitchLoadedPage: schalen, verplaatsen, tekenen
N-up impositie is het vriendelijkere probleem, omdat niets wordt verborgen, alleen herschikt. StitchLoadedPage neemt een doelpagina-index, een bronpagina-index, een X/Y-offset en een schaalfactor, en tekent de bronpagina op die positie en grootte op de doelpagina:
De operatorstring die de methode toevoegt is een standaard transform-and-paint-volgorde: q om de graphics state op te slaan, een cm-matrix met de schaal op de diagonaal en de offset in de translatievakken, /StitchSrc Do om een extern object op te roepen, en Q om de state te herstellen. De q/Q-combinatie is belangrijk: die isoleert de transform zodat de gestikte pagina zijn coördinatensysteem niet lekt naar alles wat daarna wordt toegevoegd. De methode bewaakt ook de voor de hand liggende fouten, indexen buiten bereik, een doel dat gelijk is aan de bron, een niet-positieve schaal, die wordt geklemd op 1.0, en geeft stilletjes terug in plaats van een exception te gooien, dus controleer je invoer want een stille no-op ziet er precies uit als succes
StitchLoadedPageSideBySide is een dunne convenience wrapper boven de algemene methode. Hij leest de breedte van de media box van de doelpagina, deelt die door twee en roept StitchLoadedPage aan met die halve breedte als X-offset en een vaste schaal van 0.5, zodat de bron op de rechterhelft komt te staan. Die hard-coded 0.5 gaat ervan uit dat bron en doel dezelfde breedte hebben; als dat niet zo is, vult de bron zijn helft niet netjes en wil je de algemene StitchLoadedPage gebruiken met een schaal die je zelf uit beide media boxes berekent
De vereenvoudigde XObject-strategie en de ISO-afruil
Hier maakt de implementatie een bewuste kortere route die je moet kennen voordat je de uitvoer op alle viewers vertrouwt. Een correcte N-up impositie wikkelt de content van de bronpagina in een Form XObject, een zelfstandig tekenbaar object dat volgens ISO 32000-1 §8.10.1 /Type /XObject, /Subtype /Form en een eigen /BBox-clippingbox moet hebben. De stitch-functie uit round negen bouwt die wrapper niet. In plaats daarvan registreert hij de bron page dictionary zelf direct onder de /Resources /XObject van de doelpagina met de naam StitchSrc, en tekent die vervolgens met Do. Een page dict en een Form XObject delen genoeg van hun contentmodel, beide verwijzen naar een content stream en een resourcedictionary, dat veel readers het resultaat zullen renderen
Maar het is geen conforme Form XObject. Hij mist de /Subtype /Form-markering en zijn eigen /BBox, wat betekent dat een strikte consumer het recht heeft om de Do te negeren of hem anders te clippen dan je verwacht. De TechnicalNotes voor deze ronde zeggen dat ook expliciet: de aanpak "rendert onder de meeste readers" maar is "not a strictly ISO-compliant Form XObject", en volledige conformiteit vereist dat je als aparte stap een echte Form XObject-stream synthetiseert. Behandel stitch-uitvoer daarom zoals elk niet-conform construct: verifieer hem in de specifieke viewers die je klanten gebruiken, niet alleen in die op je eigen machine, en vertrouw voor archiefwaardige of streng gevalideerde PDF's niet op dit pad. Dezelfde discipline geldt voor alles wat je op de geladen objectgraph bouwt, en daarom verdient een PDF preflight-pass in Delphi zijn plek in de releasepipeline zodra je documenten programmatisch wijzigt
Waar deze passen, en waar niet
Beide methoden zijn content-streamgereedschap, dus het mentale model is hetzelfde als bij direct tekenen. Als je pagina's vanaf nul hebt gebouwd met de component, zullen de vector- en kleuroperators achter deze aanroepen vertrouwd aanvoelen vanuit HotPDF canvas drawing in Delphi; het verschil is alleen dat je hier toevoegt aan een stream die iemand anders heeft geschreven in plaats van aan een stream die jij bezit. Houd drie grenzen in gedachten:
- Redactie is cosmetisch.
RedactLoadedRectschildert over content heen en verwijdert hem nooit. Voor iets gevoeligs moet je de bron opnieuw genereren of echte contentverwijdering gebruiken, een zwarte balk is geen beveiliging - Stitch is bewust niet-conform. De bronpagina wordt als een pseudo-XObject gerefereerd zonder de §8.10.1
/Subtype /Formen/BBox, dus controleer de rendering in je doelviewers en vermijd dit wanneer strikte validatie vereist is - Coördinaten zijn pagina-user-space. Oorsprong linksonder, punten, gestuurd door de eigen media box van de pagina. Lees de box met
GetLoadedPageBoxvoordat je iets plaatst, want de pagina die je hebt geladen is misschien niet het formaat dat je aannam
Binnen die grenzen dekt de combinatie een echte workflow af: pagina's herschikken voor afdrukken, niet-vertrouwelijke gebieden maskeren en het resultaat weer wegschrijven met SaveLoadedDocument, allemaal zonder een volledige her-render. De loaded-document-API met deze stitch- en mask-primitives wordt meegeleverd met de HotPDF Component voor Delphi en C++Builder, samen met de form-field-, annotation- en FDF-methoden uit dezelfde release
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;// 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);