Техническа статия

Истинско редактиране (Redaction) на PDF в Delphi: Изтриване и почистване

Реалното редактиране (redaction) на PDF в Delphi означава изтриване на подлежащите байтове, а не просто рисуване върху тях. Библиотеката losLab PDF Library поставя тази граница с два приложни програмни интерфейса (API): RedactRegion редактира потока от съдържание на страницата, така че премахнатият текст, вектори и изображения физически изчезват, а SanitizeDocument изчиства метаданните, скриптовете и действията, където също се крие редактирана информация. Ако сгрешите в която и да е част, рискувате да изпратите файл, който изглежда цензуриран на екрана, но позволява тайната да бъде прочетена от всеки, който копира текста или отвори свойствата на документа

Този тип грешка е публично известен факт. Вестници, съдилища и държавни агенции са разпространявали PDF файлове с черен правоъгълник, поставен върху име или число, само за да може читателят да маркира текста под него, да го копира и да го постави напълно четлив на друго място. Правоъгълникът е бил просто рисунка. Текстът все още е там, намиращ се в потока със съдържание един слой под рисунката, напълно непокътнат и избираем. losLab PDF Library – PDF ядрото за Delphi и C++Builder, достъпно чрез класа TPDFlib – разглежда тази разлика като същността на редактирането, а не като закъсняло решение

Защо рисуването на черен правоъгълник върху PDF текст не е реално редактиране?

Библиотеката losLab PDF Library предлага метода MaskRect за случаите, в които покриването е точно това, което искате: воден знак, визуално затъмнение или печат за одобрение. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) добавя правоъгълник към потока от съдържание на страницата и го оцветява с оператора за изчертаване на пътека re, последван от оператора за запълване f (ISO 32000-1 §8.5). Всеки байт, който вече е бил на страницата – глифове на името, което сте покрили, векторните линии, вграденото изображение – остава точно там, където е бил, една инструкция по-рано в същия поток. Съвместимият четец изчертава черното поле последно, така че то надделява визуално. Покритието не променя нищо под него

Всеки може да възстанови оригиналното съдържание по три начина: да маркира и копира текста директно през непрозрачното поле, да пусне софтуер за извличане на текст над страницата или да изтрие покриващия правоъгълник от потока със съдържание. Методът MaskRect е честен в наименованието си – той маскира, а не премахва. Резултатът е стикер върху дума, която все още можете да прочетете, като просто отлепите стикера

Как RedactRegion изтрива съдържание на ниво инструкции

Методът RedactRegion(Page, Left, Top, Width, Height, Options) работи един слой под рисунката. losLab PDF Library анализира всеки слой със съдържание на страницата в TPDFContentProgram, обхожда списъка с инструкции с CTM-проследяващ анализатор (FindInstructionsInRect в модела на съдържанието) и маркира всеки оператор за показване на текст, чертане на пътека и изображения (Do), чиято изчертана област пресича вашия правоъгълник. Тези инструкции се премахват с TPDFContentProgram.Delete, а редактираният слой се записва обратно с WritePageContentLayer. Байтовете не се плъзгат зад капак – те напускат потока. Инструмент за извличане на текст, пуснат след това, не намира нищо, защото няма какво да бъде намерено

Две подробности по имплементацията си струва да се отбележат. RedactRegion приема правоъгълник с горен ляв ъгъл в текущата начална точка и единици и го преобразува вътрешно в долното ляво потребителско пространство, което се използва от потока със съдържание, така че вие указвате областта по начина, по който я виждате на страницата. Когато правоъгълникът обхваща няколко инструкции, RedactRegion ги изтрива в низходящ ред на индекса, тъй като премахването на инструкция 4 преди инструкция 7 би изместило всеки следващ индекс и би повредило обхвата. Предайте Options = 0 и losLab PDF Library също така ще постави плътно черно поле над изчистения регион като видим маркер – но този маркер е козметичен, приложен след като реалното съдържание вече е изтрито

На практика рядко се налага да кодирате твърдо правоъгълника. Вие търсите в страницата за чувствителния низ и прочитате неговата ограничаваща кутия (bounding box) – същият процес на търсене и изброяване на елементи, описан в статията за намиране на текст и елементи на страница в Delphi – и след това предавате този правоъгълник на RedactRegion:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Delete everything painted inside a 220 x 14 pt box on page 1.
      // Coordinates are top-left, in the current origin and units.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 stamps a black marker
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Къде все още се крие редактирана информация в PDF файл?

Редактирането, което спира до видимия слой, оставя следи, тъй като PDF файлът съхранява информация на места, които читателят никога не вижда. losLab PDF Library разделя тези източници на изтичане на информация на няколко важни канала. Речникът с информация за документа (Document Information Dictionary) и потокът от XMP метаданни редовно съдържат име на Автор (Author), Създател (Producer) или Ключови думи (Keywords), които назовават точно лицето или случая, които сте премахнали от текста. JavaScript на ниво документ и каталогът OpenAction могат да се изпълнят при отваряне и да достигнат до данни, които смятате за изтрити. Всяка анотация съдържа незадължително действие /A, а самата анотация – бележка, старо поле от формуляр, линк – може да се намира директно над редактираното място със собствено копие на текста. Нищо от това не се засяга от шифрирането; шифриран файл с обикновен текстов XMP пакет пак ще издаде заглавието си на търсачките – пропуск, който си струва да проверите при одитиране на шифриране и права за достъп. Надеждното изчистване трябва да обхване всяко от тези скривалища, а не само страницата, която разглеждате

Почистване на документа със SanitizeDocument

losLab PDF Library отговаря на тези източници на изтичане с метода SanitizeDocument(Flags) – организирано извикване, което изпълнява набор от независими операции по премахване, избрани чрез битова маска, и връща броя на приложените операции. Флаговете могат да се комбинират. SANITIZE_JAVASCRIPT премахва всеки JavaScript пакет на ниво документ и всеки скрипт OpenAction. SANITIZE_ACTIONS премахва речника за действие /A от всяка анотация чрез новия метод TPDFAnnots.RemoveAnnotAction и изчиства OpenAction на каталога. SANITIZE_METADATA изтрива Author, Subject, Keywords, Creator и Producer както в Document Information Dictionary (ISO 32000-1 §14.3.3), така и в потока от XMP метаданни (§14.3.2). SANITIZE_ANNOTATIONS изтрива всяка анотация в целия документ, а SANITIZE_OPEN_ACTION премахва записа /OpenAction на каталога. SANITIZE_ALL е побитовото обединение на петте флага

Конвенциите за индексиране в тези премахвания са точно от типа логика, която тихомълком прекъсва ръчно написан цикъл – това е и основната причина SanitizeDocument да съществува като едно извикване. Списъкът GlobalJavaScriptPackageName е 0-базиран, а RemoveGlobalJavaScript свива списъка в движение, така че скриптът за премахване на JavaScript винаги чете пакет с индекс 0 и премахва, докато броят им достигне нула. От друга страна, DeleteAnnotation е 1-базиран. AnnotationCount отчита спрямо текущо избраната страница, така че процесът за анотации избира всяка страница по ред преди броенето. losLab PDF Library Corectно управлява тези граници вътрешно, така че вие само предавате един набор от флагове и получавате обратно броя на извършените промени, вместо да възпроизвеждате пет различни правила за обхождане

var
  Applied: Integer;
begin
  // Strip metadata, scripts, and actions but keep the annotations:
  Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
    SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);

  // Or clear every hidden channel, annotations included:
  Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;

Пълният работен процес по редактиране и почистване

Обединете двете стъпки и пълната процедура е кратка. losLab PDF Library премахва визуалното съдържание с RedactRegion страница по страница, след което изчиства всеки скрит канал със SanitizeDocument(SANITIZE_ALL) перед записване на файла. Тъй като редактирането почти винаги се извършва върху документи, пристигнали извън вашия контрол, си струва да ги заредите през защитен път – същата предпазна мярка, описана в статията за безопасно парсване на ненадеждни PDF файлове – така че повреден входящ файл да се провали чисто, а не по средата на процеса на редактиране

var
  PDF: TPDFlib;
  Applied: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('incoming.pdf', '') = 1 then
    begin
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);       // delete the sensitive line
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // clear metadata, JS, actions, annots
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Къде спира редактирането на потока от съдържание

RedactRegion работи върху потока от съдържание, което е точно мястото, където живее цифровият текст, и където сканираният текст не съществува. Когато страницата е сканирано изображение, думите са пиксели в рамките на графичен обект (image XObject), а не текстови оператори, и losLab PDF Library не може да изреже име от средата на растерното изображение по начина, по който изтрива глиф. Работата с изображения е преднамерено консервативна: всяко изображение, чието местоположение пресича правоъгълника, се премахва изцяло, така че прозорец за редактиране върху сканирана страница изтрива целия сканиран файл, а не просто отрязък от него. Редактирането на регион в изображение изисква растеризиране, рисуване и повторно вграждане на ниво пиксели, което е различна операция от изтриването на поток от съдържание. Същото предупреждение важи и за OCR текстов слой над сканирана страница: RedactRegion премахва текстовите оператори, които вижда, но картината под тях остава непокътната, освен ако нейният image XObject също не пресича правоъгълника

Два по-фини детайла гарантират точността на резултатите. Тестът за пресичане изчислява приблизително ширината на всеки текстов фрагмент въз основа на броя знаци и размера на шрифта, вместо да измерва точните метрики на глифа, така че много кратка дума в пропорционален шрифт може да се изплъзне през плътно очертан правоъгълник – оставете малка граница около полето. Изтриването също така е с точност до инструкция: RedactRegion премахва цели оператори за показване на текст, така че правоъгълник, който обхваща само част от фрагмента, може да изтрие и съседни знаци, които споделят същия оператор с него. Нито едно от тези ограничения не е причина да нямате доверие на механизма, а е просто основание да очертавате правоъгълника внимателно и да проверявате крайния резултат

Правилото, което затваря всеки работен процес по редактиране, е проверката, а не сляпото доверие. Редактирайте региона, почистете документа, запишете в нов файл, отворете го отново и се опитайте да извлечете както премахнатия текст, така и изчистените метаданни; когато и двете се върнат празни, редактирането е успешно. Приложните програмни интерфейсове (API) RedactRegion и SanitizeDocument, показани тук, се доставят с losLab PDF Library за Delphi и C++Builder, заедно с пълната документация за модела на съдържанието и почистването