Получавате задача: вземете пач вече визуализирани извлечения, замажете номерата на сметките и подайте по две страници на лист, за да спестите хартия. И двете половини на тази задача са хирургия върху content-stream в PDF, който не сте създали, така че няма удобен page canvas, върху който да рисувате, и няма font manager, на който да разчитате. Редактирате директно обектния граф на зареден документ, като добавяте сурови drawing оператори към страница, която е била подредена от друг инструмент. HotPDF предлага точно две входни точки за това и по-опасната от двете е тази, която изглежда безобидна
HotPDF е нативен VCL PDF компонент за Delphi и C++Builder. В неговия API за зареден документ в round nine бяха добавени първите методи, които създават изцяло ново съдържание върху страница, която сте отворили от диск, а не върху такава, която сте изградили от нулата. Два от тях са темата тук: RedactLoadedRect, който рисува плътен правоъгълник върху област, и StitchLoadedPage, който мащабира една страница и я рисува върху друга. И двата работят, като записват ISO 32000-1 §8.5 content-stream оператори в stream-а на страницата. Разбирането на това какво правят тези оператори и, също толкова важно, какво не правят, е разликата между работещ инструмент и изтичане на данни./Contents stream-а. Разбирането на това какво правят тези оператори и, също толкова важно, какво не правят, е разликата между работещ инструмент и изтичане на данни
Добавяне на оператори към заредена страница
Когато изграждате страница с обичайния HotPDF API, компонентът притежава content stream-а и сериализира вашите TextOut и vector извиквания вместо вас. Заредената страница е различна: нейният /Contents е съществуващ stream object, възможно споделен, възможно част от content array, и трябва да се вмъкнете в него, без да повредите това, което вече е там. Round nine въведе три малки помощника, които правят това безопасно. NewIndirectStream заделя нов indirect THPDFStreamObject с празен буфер и /Length 0 запис; ResolveLoadedStream следва indirect reference надолу до подлежащия stream; и AppendLoadedStream записва сурови байтове в края на stream-а и пренаписва /Length така че записаният обект да остане добре оформен
И двата публични метода следват един и същ модел. Намерете /Contents на страницата, разрешете го до stream и ако няма използваем stream, създайте нов и го прикачете. После добавете операторите. Понеже новите байтове отиват в края на stream-а, моделът на пейнтера гарантира, че те се рисуват върху всичко, което оригиналното оформление е начертало. Този ред е целият механизъм зад redaction правоъгълника, а също и причината този правоъгълник да не е това, което повечето хора предполагат
RedactLoadedRect: плътен капак, не изтриване
RedactLoadedRect приема нулево базиран индекс на страница, четири координати в user space и три цветови компонента в диапазона 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;
Под капака методът изписва три оператора в content stream-а: задаване на fill color в DeviceRGB (r g b rg), path за правоъгълник (x y w h re), и fill (f). Ширината и височината се извеждат като X2 - X1 и Y2 - Y1, така че подавате две срещуположни ъгълни точки и оставяте метода да изчисли обхвата. Подайте 0, 0, 0 за цвят и получавате черна лента; подайте 1, 1, 1 за бяла, която съвпада с бяла страница. Координатите са собственото user space на заредената страница, което означава, че началото е долният ляв ъгъл и мерните единици са points, и също означава, че трябва да знаете /MediaBox на страницата, за да поставите нещо точно; GetLoadedPageBox с pbMediaBox ви дава това
Прочетете това два пъти: запълненият правоъгълник покрива съдържание визуално, но не го премахва. Текстът, изображението или vector art-ът под правоъгълника все още присъства в PDF-а, все още е в обектния граф, все още е извличаем от всеки, който копира страницата, пусне text extractor или просто изтрие вашия правоъгълник от content stream-а. Това е визуално маскиране, не redaction в правен или сигурностен смисъл. Ако скривате наистина чувствителни данни - номера на сметки, медицински досиета, самоличности, каквото и да е регулирано - да ги покриете с черна кутия и да изпратите файла е изтичане на данни, което чака да бъде открито. Истинското redaction изисква изтриване на подлежащите content objects, не рисуване отгоре върху тях
The method name says "Redact", and that is a useful warning about how the result will be misread, not a promise about what it deletes. The implementation is honest about this in its own comment: it calls itself the "visual redaction primitive" and notes that content-removing redaction needs a content-stream interpreter that walks and rewrites the existing operators. HotPDF's loaded-document path does not do that here. So the safe rule is narrow: use RedactLoadedRect for non-sensitive cosmetic masking — hiding a draft watermark, blanking a region before a screenshot, covering an obsolete logo on an internal proof. The moment the thing under the box would matter if it leaked, this method is the wrong tool, and the right answer is to regenerate the document without the data or to use a real content-removal pipeline
StitchLoadedPage: мащабиране, преместване, рисуване
N-up imposition is the friendlier problem because nothing is hidden, only rearranged. StitchLoadedPage takes a target page index, a source page index, an X/Y offset, and a scale factor, and it draws the source page onto the target at that position and size:
// 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);
The operator string it appends is a standard transform-and-paint sequence: q to save graphics state, a cm matrix carrying the scale on the diagonal and the offset in the translation slots, /StitchSrc Do to invoke an external object, and Q to restore state. The q/Q pair matters: it isolates the transform so the stitched page does not bleed its coordinate system into anything appended afterward. The method also guards the obvious mistakes — indices out of range, a target equal to the source, a non-positive scale (which it clamps to 1.0) — and exits quietly rather than raising, so check your inputs because a silent no-op looks identical to success
StitchLoadedPageSideBySide is a thin convenience over the general method. It reads the target's media-box width, halves it, and calls StitchLoadedPage with that half-width as the X offset and a fixed scale of 0.5, putting the source on the right half. That hard-coded 0.5 assumes the source and target share a width; if they do not, the source will not fill its half cleanly, and you will want the general StitchLoadedPage with a scale you compute yourself from both media boxes
Опростената XObject стратегия и нейният ISO компромис
Here is where the implementation makes a deliberate shortcut you need to know about before you trust the output across viewers. A correct N-up imposition wraps the source page's content in a Form XObject — a self-contained drawable object that ISO 32000-1 §8.10.1 says must carry /Type /XObject, /Subtype /Form, and its own /BBox clipping box. HotPDF's round-nine stitch does not build that wrapper. Instead it registers the source page dictionary itself directly under the target's /Resources /XObject with the name StitchSrc, then draws it with Do. A page dict and a Form XObject share enough of their content model — both reference a content stream and a resource dictionary — that many readers will render the result
But it is not a conforming Form XObject. It lacks the /Subtype /Form marker and its own /BBox, which means a strict consumer is within its rights to ignore the Do or to clip it differently than you expect. The TechnicalNotes for this round say so plainly: the approach "renders under most readers" but is "not a strictly ISO-compliant Form XObject", and full compliance requires synthesizing a real Form XObject stream as a separate step. So treat the stitch output the way you would treat any non-conforming construct: verify it in the specific viewers your customers run, not just the one on your machine, and if you need archival or strict-validator-clean PDFs, do not rely on this path. The same discipline applies to anything you build on the loaded object graph, which is why a PDF preflight pass in Delphi earns its place in the release pipeline whenever you mutate documents programmatically
Къде се вписват и къде не
Both methods are content-stream tools, so the mental model is the same one you use for direct drawing. If you have built pages from scratch with the component, the vector and colour operators behind these calls will look familiar from HotPDF canvas drawing in Delphi; the difference is only that here you are appending to a stream someone else authored rather than one you own. Keep three boundaries in mind:
- Redaction is cosmetic.
RedactLoadedRectpaints over content and never deletes it. For anything sensitive, regenerate the source or use real content removal — a black box is not security - Stitch is non-conforming by design. The source page is referenced as a pseudo-XObject without the §8.10.1
/Subtype /Formand/BBox, so confirm rendering in your target viewers and avoid it where strict validation is required - Coordinates are page user space. Bottom-left origin, points, driven by the page's own media box. Read the box with
GetLoadedPageBoxbefore you place anything, because the page you loaded may not be the size you assumed
Used within those limits, the pair covers a real workflow: rearrange pages for printing, mask non-confidential regions, and write the result back with SaveLoadedDocument — all without a full re-render. The loaded-document API that includes these stitch and mask primitives ships with the HotPDF Component for Delphi and C++Builder, alongside the form-field, annotation, and FDF methods from the same round