Как изменить поле /Producer в pdf документе без сторонних библиотек

104
26 сентября 2021, 09:40

Есть pdf документ. Я использую следующий код, для того, чтобы его открыть и изменить значение /Producer

public void CorrectPdf()
{
    string path = "c:\temp\My.pdf");
    byte[] bf = File.ReadAllBytes(path);
    string s = Convert.ToBase64String(bf);
    var decodeStr = Base64Decode(s);
    //Здесь будет корректировка строки
    //decodeStr.Replace(@"/Producer (HiQPdf 11.1)", "/Producer (MyMy)");
    var encodeStr= Base64Encode(decodeStr);
    var bf = Convert.FromBase64String(encodeStr);
    System.IO.File.WriteAllBytes(path, bf);
}
    public static string Base64Encode(string plainText)
    {
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
        return System.Convert.ToBase64String(plainTextBytes);
    }
    public static string Base64Decode(string base64EncodedData)
    {
        var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
        return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
    }

Не понимаю почему на выходе pdf документ с пустыми листами.

Answer 1

И хотелось бы получить полный ответ почему я немгу pdf считать в байты, затем перевести в строку

На это ответ очень прост: PDF - бинарный формат, а не текстовый. Любой код, который вызывает Encoding.GetString на содержимом PDF-файла (в целом) можно смело выбрасывать. Конечно, преобразовывать в строку отдельные его поля, которые гарантированно являются текстом, можно.

Если вам нужно просто грубо заменить вхождение /Producer (Foo) на /Producer (Bar), не учитывая структуру файла (т.е., если такая последовательность случайно встретится в содержимом файла, он будет поврежден), то можно сделать так:

using System;
using System.IO;
using System.Text;
class Program
{
    public static bool SequenceEquals(byte[] array, int position, byte[] match)
    {
        if (match.Length > (array.Length - position)) return false;
        for (int i = 0; i < match.Length; i++)
        {
            if (array[position + i] != match[i])  return false;
        }
        return true;
    }
    public static int FindSequence(byte[] arr, byte[] match, int startindex=0)
    {
        for (int i = startindex; i < arr.Length; i++)
        {
            if (SequenceEquals(arr, i, match)) return i;
        }
        return -1;
    }
    public static void Main()
    {        
        string path1 = @"C:\Test\file1.pdf";
        string path2 = @"C:\Test\file2.pdf";
        //считаем данные
        byte[] data = File.ReadAllBytes(path1);
        //подготовим последовательности для поиска
        byte[] match_start = Encoding.ASCII.GetBytes("/Producer");
        byte[] match_end = Encoding.ASCII.GetBytes(")");
        //найдем последовательность в массиве
        int index_start = FindSequence(data, match_start);           
        int index_end = FindSequence(data, match_end, index_start + 1) + 1;
        byte[] bytes_old = new byte[index_end - index_start];
        Array.Copy(data, index_start, bytes_old, 0, bytes_old.Length);
        string s_old = Encoding.ASCII.GetString(bytes_old);
        //подготовим новые данные
        string s_new = "/Producer (MyMy)";
        byte[] bytes_new = Encoding.ASCII.GetBytes(s_new);
        byte[] newdata = new byte[data.Length - bytes_old.Length + bytes_new.Length];
        //запишем результат
        Array.Copy(data, newdata, index_start);
        Array.Copy(bytes_new, 0, newdata, index_start, bytes_new.Length);
        Array.Copy(data, index_end, newdata, index_start+ bytes_new.Length, data.Length - index_end);
        File.WriteAllBytes(path2, newdata);
        Console.ReadKey();
    }
}

Корректная замена должна учитывать структуру файла в соответствии с документацией Adobe.

Answer 2

Пожалуйста, не плюсуйте это сообщение, т. к. оно не является точным ответом на вопрос. Минусовать можно.

Рассмотрим ваш код по шагам.
Считали файл как массив байтов:

byte[] bf = File.ReadAllBytes(path);

Конвертировали массив байтов в base64:

string s = Convert.ToBase64String(bf);

Внутри метода Base64Decode тут же конвертировали эту base64-строку обратно в байты:

var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);

То есть содержимое массивов base64EncodedBytes и bf одинаково. Можно смело выкидывать эти действия.

Далее массив байтов преобразуется в строку:

return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);

Я не спец в PDF, однако быстрый поиск показывает, что внутри PDF могут быть разные кодировки. И не факт, что у вашего файла UTF-8. Поэтому вы могли получить кракозябры вместо текста.

После замены вы перегоняете строку в байты кодировки UTF-8.
Далее вы опять совершаете ненужные действия: массив байтов конвертируется в base64 и тут же обратно из base64 в массив байтов.

Как я понимаю, эти ненужные конвертации ничего не должны портить. Но они просто лишние и создают нагрузку на процессор и память.

Вообще, незачем читать сперва байты, а потом перекодировать их в текст в нужной кодировке. Можно это сделать сразу, вызовом одного метода:

var text = File.ReadAllText(path, Encoding.UTF8);

Тут делаем замену: text = text.Replace.
Записываем аналогично:

File.WriteAllText(path, text, Encoding.UTF8);

Естественно, кодировки должны совпадать при чтении и записи.

Можно попробовать брутфорс: перебрать все кодировки.

foreach (var encoding in Encoding.GetEncodings())

Их довольно много. У меня показывает 140 штук.

Никто больше не отвечает, так что буду отдуваться.

Почему простая строковая замена не работает? Повторю, что я не спец в PDF. Но там вполне может быть структура данных с полями определённых размеров. И когда вы делаете замену:

decodeStr.Replace(@"/Producer (HiQPdf 11.1)", "/Producer (MyMy)");

то очевидно, что длины строк не совпадают. Из-за этого весь текст сдвигается. Но это же не просто текст - это структура данных со множеством полей, где хранится разметка страниц, шрифты, изображения и многое другое. В результате сдвига эти данные сломались. В итоге вы получаете пустые листы.

Самый лучший способ решения проблемы - взять библиотеку для работы с PDF, о чём уже писали в комментариях.

READ ALSO
Как использовать переменные заданные в другом файле?

Как использовать переменные заданные в другом файле?

Есть определенные переменные, которые везде одинаковые, и которые можно вынести в отдельный файлКак мне в файле config

176
Поиск значения внутри масссива

Поиск значения внутри масссива

Коллеги, добрый деньЯ недавно занимаюсь разработкой на php и столкнулся с подобной проблемой

271
Вывод в цикле лишь одного элемента (php) с определенным id

Вывод в цикле лишь одного элемента (php) с определенным id

есть цикл, в нем выводяться записи с базы данных, в нем есть следующее условие "{% if loopindex != 1 and loop

108