ITextSharp 4.1.6 - Удалить существующую строку из шаблона PDF

У меня есть шаблон PDF, созданный в LibreOffice, и я заполняю его с помощью AcroFields. В некоторых редких случаях я хотел бы скрыть определенное поле, поэтому я удаляю его с помощью метода RemoveField. Однако его граница остается там. Из того, что я гуглил, кажется, что LibreOffice создает формы именно так.

Что я придумал до сих пор, так это получить прямоугольник поля и покрыть его белым изображением. Однако проблема заключается в том, что клиент планирует создавать шаблоны с использованием фонового изображения и/или другого цвета фона, кроме белого, что делает мое текущее решение практически непригодным для использования.

Поэтому вопрос в том, можно ли как-то удалить границы? [например. путем доступа к некоторой низкоуровневой объектной модели ITextSharp или что-то в этом роде]

Заранее большое спасибо


person Bruno Laurinec    schedule 25.03.2013    source источник


Ответы (1)


Удаление отдельных объектов рисования может быть немного сложным, но не невозможным. Самое сложное — решить, какие объекты вы хотите удалить. Ниже приведен пример кода для iTextSharp 4.1.6, который сначала создает PDF-файл с двумя прямоугольниками, а затем создает второй PDF-файл на основе первого с удаленным одним из прямоугольников. Вам нужно будет применить свою логику, чтобы выяснить, какой прямоугольник вы хотите удалить. Возможно, у вас на самом деле не прямоугольники, а линии, которые образуют прямоугольник, в этом случае вам также потребуется немного изменить код.

Этот первый бит просто создает базовый PDF-файл на рабочем столе с двумя прямоугольниками:

//Create a file on the desktop with two rectangles
var file1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "File1.pdf");
using (var fs = new FileStream(file1, FileMode.Create, FileAccess.Write, FileShare.None)) {
    var doc = new Document();
    var writer = PdfWriter.GetInstance(doc, fs);
    doc.Open();

    var cb = writer.DirectContent;

    //Draw two rectangles
    cb.SaveState();
    cb.SetColorStroke(iTextSharp.text.Color.RED);
    cb.Rectangle(40, 60, 200, 100);
    cb.Stroke();
    cb.RestoreState();

    cb.SaveState();
    cb.SetColorStroke(iTextSharp.text.Color.BLUE);
    cb.Rectangle(500, 80, 90, 50);
    cb.Stroke();
    cb.RestoreState();

    doc.Close();
}

Эта следующая часть является более сложной частью. Я рекомендую вам выполнить Console.WriteLine(tokenizer.StringValue); внутри цикла while, чтобы увидеть все команды PDF. Вы заметите, что они используют синтаксис RPN, к которому нужно немного привыкнуть. Дополнительные вопросы см. в комментариях к коду.

//Bind a reader to our first file
var reader = new PdfReader(file1);
//Get the first page (this would normally be done in a loop)
var page = reader.GetPageN(1);
//Get the "contents" of that page
var objectReference = (PdfIndirectReference)page.Get(PdfName.CONTENTS);
//Get the actual stream of the "contents"
var stream = (PRStream)PdfReader.GetPdfObject(objectReference);
//Get the raw bytes of the stream
var streamBytes = PdfReader.GetStreamBytes(stream);
//Convert the bytes to actual PDF tokens/commands
var tokenizer = new PRTokeniser(new RandomAccessFileOrArray(streamBytes));
//We're going to re-append each token to this below buffer and remove the ones that we don't want
List<string> newBuf = new List<string>();
//Loop through each PDf token
while (tokenizer.NextToken()) {
    //Add them to our master buffer
    newBuf.Add(tokenizer.StringValue);
    //The "Other" token is used for most commands, so if we're on "Other" and the current command is "re" which is rectangle
    if (
        tokenizer.TokenType == PRTokeniser.TK_OTHER && //The "Other" token is used for most commands
        newBuf[newBuf.Count - 1] == "re" &&            //re is the rectangle command
        newBuf[newBuf.Count - 5] == "40"               //PDFs use RPN syntax so the red rectangle command was "40 60 200 100 re"
        ) {
        newBuf.RemoveRange(newBuf.Count - 5, 5);       //If the above conditions were met remove the last 5 commands
    }
}

//Convert our array to a string with newlines between each token, convert that to an ASCII byte array and push that back into the stream (erasing the current contents)
stream.SetData(System.Text.Encoding.ASCII.GetBytes(String.Join("\n", newBuf.ToArray())));

//Create a new file with the rectangle removed
var file2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "File2.pdf");
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None)) {
    //Bind a stamper to our read above which has the altered stream
    var stamper = new PdfStamper(reader, fs);
    //Loop through each page
    int total = reader.NumberOfPages;
    for (int i = 1; i <= total; i++) {
        //Push the content over page by page
        reader.SetPageContent(i, reader.GetPageContent(i));
    }
    stamper.Close();
}
reader.Close();
person Chris Haas    schedule 26.03.2013
comment
ВОТ ЭТО ДА!!! Этот код действительно помог мне достичь цели, которую я считал почти невозможной. Спасибо миллион Крис !!! - person Bruno Laurinec; 26.03.2013
comment
@brunolau, имейте в виду, что для универсального метода удаления объектов вам придется распознавать гораздо больше рассматриваемого потока. Кроме того, вам также придется проверять xobjects, а не только непосредственный поток содержимого страницы. Код, представленный здесь, является только стартовым. - person mkl; 27.03.2013