En förfrågan landar på ditt bord: ta en bunt redan renderade rapporter, sudda ut kontonumren och skicka två sidor per ark för att spara papper. Båda halvorna av uppgiften är content-stream-kirurgi på en PDF du inte har skapat, så det finns ingen vänlig sidduk att rita på och ingen font manager att luta sig mot. Du redigerar objektgrafen i ett inläst dokument direkt och lägger råa ritoperatorer till en sida som något annat verktyg har lagt upp. HotPDF exponerar exakt två ingångar för detta, och den farligare av de två är den som ser harmlös ut
HotPDF är en inbyggd VCL-PDF-komponent för Delphi och C++Builder. I den nionde omgången av loaded-document-API:t infördes de första metoderna som skapar helt nytt innehåll på en sida du öppnat från disk i stället för en du byggt från grunden. Två av dem är i fokus här: RedactLoadedRect, som målar en ogenomskinlig rektangel över ett område, och StitchLoadedPage, som skalar en sida och ritar den på en annan. Båda arbetar genom att skriva ISO 32000-1 §8.5 content-stream-operatorer i sidans /Contents stream. Att förstå vad de operatorerna gör, och lika viktigt vad de inte gör, är skillnaden mellan ett fungerande verktyg och ett dataintrång
Lägga till operatorer på en inläst sida
När du bygger en sida med det vanliga HotPDF-API:t äger komponenten content stream och serialiserar dina TextOut och vektor-anrop åt dig. En inläst sida är annorlunda: dess /Contents är ett befintligt streamobjekt, kanske delat, kanske en del av en content array, och du måste skarva in dig utan att förstöra det som redan finns. Omgång nio införde tre små hjälpmetoder som gör det säkert. NewIndirectStream allokerar ett nytt indirekt THPDFStreamObject med en tom buffer och en /Length 0 post; ResolveLoadedStream följer en indirekt referens ned till det underliggande streamobjektet; och AppendLoadedStream skriver råa byte i slutet av streamet och skriver om /Length så att det sparade objektet förblir välformat
Mönstret som båda publika metoder följer är detsamma. Hitta sidans /Contents, lös upp den till ett streamobjekt, och om det inte finns något användbart streamobjekt, skapa ett och koppla det. Lägg sedan till operatorerna. Eftersom de nya byten hamnar i slutet av streamet garanterar painter's model att de renderas ovanpå allt det ursprungliga layouten ritade. Den ordningen är hela mekanismen bakom maskeringsrektangeln, och den är också orsaken till att rektangeln inte är vad de flesta tror att den är
RedactLoadedRect: ett ogenomskinligt täckskikt, inte en radering
RedactLoadedRect tar ett sidindex från noll, fyra koordinater i användarrymden och tre färgkomponenter i intervallet 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;
Under huven skickar metoden tre operatorer till content streamet: en fyllfärg satt i DeviceRGB (r g b rg), en rektangelbana (x y w h re), och en fyllning (f). Bredd och höjd beräknas som X2 - X1 och Y2 - Y1, så du skickar två motsatta hörn och låter metoden räkna ut utbredningen. Ange 0, 0, 0 för färgen så får du en svart stapel; ange 1, 1, 1 för en vit som matchar en vit sida. Koordinaterna är den inlästa sidans egen användarrymd, vilket betyder att origo ligger i nedre vänstra hörnet och att enheterna är punkter, och det betyder också att du behöver sidans /MediaBox för att placera något exakt; GetLoadedPageBox med pbMediaBox ger dig det
Läs detta två gånger: en fylld rektangel täcker innehåll visuellt, den tar inte bort det. Texten, bilden eller vektorgrafiken under rektangeln finns fortfarande kvar i PDF:en, fortfarande i objektgrafen, fortfarande möjlig att extrahera för den som kopierar sidan, kör en textutdragare eller helt enkelt tar bort din rektangel från content streamet. Detta är visuell maskering, inte redigering i juridisk eller säkerhetsmässig mening. Om du döljer verkligt känslig information - kontonummer, journaluppgifter, identiteter, allt som omfattas av reglering - så är att täcka den med en svart ruta och skicka filen ett dataläckage som väntar på att upptäckas. Äkta redigering kräver att de underliggande innehållsobjekten tas bort, inte att man målar över dem
Metodnamnet säger "Redact", och det är en användbar varning om hur resultatet kommer att misstolkas, inte ett löfte om vad den tar bort. Implementeringen är ärlig om detta i sin egen kommentar: den kallar sig själv den "visuella maskeringsprimitiven" och noterar att redigering som faktiskt tar bort innehåll kräver en content-stream-tolk som går igenom och skriver om de befintliga operatorerna. HotPDFs loaded-document-väg gör inte det här. Så den säkra regeln är snäv: använd RedactLoadedRect för icke-känslig kosmetisk maskering - att dölja en utkastsvattenstämpel, tömma ett område före en skärmdump, täcka en föråldrad logotyp på ett internt korrektur. I samma ögonblick som det som ligger under rutan skulle spela roll om det läckte är metoden fel verktyg, och det rätta svaret är att återskapa dokumentet utan data eller att använda en riktig pipeline för att ta bort innehåll
StitchLoadedPage: skala, flytta, rita
N-up-sammanfogning är den vänligare uppgiften eftersom inget döljs, bara ordnas om. StitchLoadedPage tar ett mål-sidindex, ett källsidindex, en X/Y-förskjutning och en skalfaktor, och den ritar källsidan på målsidan vid den positionen och i den storleken:
// 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);
Operatorsträngen den lägger till är en vanlig transform-och-rita-sekvens: q för att spara graphics state, en cm matris som bär skalan på diagonalen och förskjutningen i translationsfälten, /StitchSrc Do för att anropa ett externt objekt, och Q för att återställa state. q/Q-paret är viktigt: det isolerar transformen så att den sammanfogade sidan inte spiller sitt koordinatsystem in i något som läggs till efteråt. Metoden skyddar också mot de uppenbara misstagen - index utanför intervallet, ett mål som är samma som källan, en icke-positiv skala (som den klämmer till 1.0) - och avslutar tyst i stället för att kasta, så kontrollera indata eftersom en tyst no-op ser identisk ut med lyckad körning
StitchLoadedPageSideBySide är en tunn bekvämlighet ovanpå den generella metoden. Den läser mål-sidans media-box-bredd, halverar den och anropar StitchLoadedPage med den halvbredden som X-förskjutning och en fast skala på 0.5, vilket placerar källan på den högra halvan. Den hårdkodade 0.5 antar att källa och mål har samma bredd; om de inte har det fyller källan inte sin halva snyggt, och då vill du använda den generella StitchLoadedPage med en skala du själv räknar ut från båda media-boxarna
Den förenklade XObject-strategin och dess ISO-avvägning
Här är platsen där implementeringen gör en medveten genväg som du behöver känna till innan du litar på resultatet i olika visare. En korrekt N-up-sammanfogning kapslar in källsidans innehåll i ett Form XObject - ett självständigt ritbart objekt som ISO 32000-1 §8.10.1 säger måste bära /Type /XObject, /Subtype /Form, och sin egen /BBox klippruta. HotPDFs stitch i omgång nio bygger inte den här omslutningen. I stället registrerar den själva sidobjektets dictionary direkt under målsidans /Resources /XObject med namnet StitchSrc, och ritar den sedan med Do. En sidobjektsdictionary och ett Form XObject delar tillräckligt mycket av sin content-modell - båda refererar till ett content stream och en resursdictionary - att många läsare kommer att rendera resultatet
Men det är inte ett Form XObject som följer standarden. Det saknar /Subtype /Form markören och sin egen /BBox, vilket betyder att en strikt konsument har rätt att ignorera Do eller att klippa det annorlunda än du väntar dig. TechnicalNotes för den här omgången säger det rakt ut: ansatsen "renderas i de flesta läsare" men är "inte ett Form XObject som följer ISO strikt", och full överensstämmelse kräver att man skapar ett riktigt Form XObject-stream som ett separat steg. Så behandla stitch-resultatet som du skulle behandla vilken icke-konform konstruktion som helst: verifiera det i de specifika visare dina kunder använder, inte bara i den på din maskin, och om du behöver arkiv- eller striktvalideringsrena PDF:er ska du inte förlita dig på den här vägen. Samma disciplin gäller allt du bygger ovanpå den inlästa objektgrafen, och därför förtjänar en PDF preflight-körning i Delphi sin plats i release-pipelinen när du muterar dokument programmässigt
Var de här passar, och var de inte gör det
Båda metoderna är content-stream-verktyg, så den mentala modellen är densamma som du använder för direkt ritning. Om du har byggt sidor från grunden med komponenten kommer vektor- och färgoperatorerna bakom dessa anrop att kännas igen från HotPDF canvas drawing in Delphi; skillnaden är bara att du här lägger till i ett stream som någon annan har författat i stället för ett du äger. Ha tre gränser i åtanke:
- Redigering är kosmetisk.
RedactLoadedRectmålar över innehåll och tar aldrig bort det. För allt känsligt ska du återskapa källan eller använda riktig content removal - en svart ruta är inte säkerhet - Stitch följer inte standarden avsiktligt. Källsidan refereras som en pseudo-XObject utan §8.10.1
/Subtype /Formoch/BBox, så bekräfta rendering i dina målvisare och undvik den där strikt validering krävs - Koordinaterna är sidans användarrymd. Origo i nedre vänstra hörnet, punkter, styrt av sidans egen media box. Läs boxen med
GetLoadedPageBoxinnan du placerar något, eftersom sidan du läst in kanske inte har den storlek du antog
Med de begränsningarna täcker paret ett verkligt arbetsflöde: ordna om sidor för utskrift, maskera icke-konfidentiella områden och skriv tillbaka resultatet med SaveLoadedDocument - allt utan en fullständig ominrendering. Loaded-document-API:t som innehåller dessa stitch- och maskeringsprimitiver levereras med HotPDF Component för Delphi och C++Builder, tillsammans med formfält-, annoterings- och FDF-metoderna från samma omgång