#pdf #itext7
#c #objective-c #macos #PDF #zlib
Вопрос:
Я использую эту функцию для поиска текста в файле PDF и замены этого текста другим текстом. Проблема в том, что когда я делаю inflate, а затем изменяю текст и сдуваю, в конечном PDF-файле иногда пропускаются некоторые тексты или графики. Это ошибка в моем коде или библиотека zlib не поддерживает это сжатие или что-то в этом роде?
// Open the PDF source file:
FILE *pdfFile = fopen([sourceFile cStringUsingEncoding:NSUTF8StringEncoding], "rb");
if (pdfFile) {
// Get the file length:
int fseekres = fseek(pdfFile, 0, SEEK_END);
if (fseekres != 0) {
fclose(pdfFile);
return nil;
}
long filelen = ftell(pdfFile);
fseekres = fseek(pdfFile, 0, SEEK_SET);
if (fseekres != 0) {
fclose(pdfFile);
return nil;
}
char *buffer = new char[filelen];
size_t actualread = fread(buffer, filelen, 1, pdfFile);
if (actualread != 1) {
fclose(pdfFile);
return nil;
}
bool morestreams = true;
while (morestreams) {
size_t streamstart = [self findStringInBuffer:buffer search:(char *)"stream" buffersize:filelen];
size_t streamend = [self findStringInBuffer:buffer search:(char *)"endstream" buffersize:filelen];
[self saveFile:buffer len:streamstart 7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
if (streamstart > 0 amp;amp; streamend > streamstart) {
streamstart = 6;
if (buffer[streamstart] == 0x0d amp;amp; buffer[streamstart 1] == 0x0a) {
streamstart = 2;
} else if (buffer[streamstart] == 0x0a) {
streamstart ;
}
if (buffer[streamend - 2] == 0x0d amp;amp; buffer[streamend - 1] == 0x0a) {
streamend -= 2;
} else if (buffer[streamend - 1] == 0x0a) {
streamend--;
}
size_t outsize = (streamend - streamstart) * 10;
char *output = new char[outsize];
z_stream zstrm;
zstrm.zalloc = Z_NULL;
zstrm.zfree = Z_NULL;
zstrm.opaque = Z_NULL;
zstrm.avail_in = (uint)(streamend - streamstart 1);
zstrm.avail_out = (uint)outsize;
zstrm.next_in = (Bytef *)(buffer streamstart);
zstrm.next_out = (Bytef *)output;
int rsti = inflateInit(amp;zstrm);
if (rsti == Z_OK) {
int rst2 = inflate(amp;zstrm, Z_FINISH);
inflateEnd(amp;zstrm);
if (rst2 >= 0) {
size_t totout = zstrm.total_out;
//search and replace text code here
size_t coutsize = (streamend - streamstart 1) * 10;
char *coutput = new char[coutsize];
z_stream c_stream;
c_stream.zalloc = Z_NULL;
c_stream.zfree = Z_NULL;
c_stream.opaque = Z_NULL;
c_stream.total_out = 0;
c_stream.avail_in = (uint)totout;
c_stream.avail_out = (uint)coutsize;
c_stream.next_in = (Bytef *)output;
c_stream.next_out = (Bytef *)coutput;
rsti = deflateInit(amp;c_stream, Z_DEFAULT_COMPRESSION);
if (rsti == Z_OK) {
rsti = deflate(amp;c_stream, Z_FINISH);
deflateEnd(amp;c_stream);
if (rsti >= 0) {
[self saveFile:coutput len:c_stream.total_out fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
delete [] coutput; coutput = 0;
[self saveFile:(char *)"nendstr" len:7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
delete[] output; output = 0;
buffer = streamend 7;
filelen = filelen - (streamend 7);
} else {
morestreams = false;
}
}
[self saveFile:buffer len:filelen fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
fclose(pdfFile);
Комментарии:
1. Вы пишете «// поиск и замена текстового кода здесь»… именно это не является тривиальным. Как объяснил Бруно, ваше поисковое слово может отсутствовать в одной последовательности символов, и символы могут располагаться в кажущемся случайном порядке. Кроме того, ваш код полностью игнорирует кодировки шрифтов до сих пор, поэтому вы, вероятно, не найдете строки, если шрифт не использует стандартную кодировку.
2. Моя проблема сейчас не в функции поиска и замены, но когда я распаковываю поток с помощью zlib и сжимаю его снова, конечный pdf отличается от исходного, отсутствует какой-либо текст или графика.
3. Вы говорите, что заменяете текст другим текстом. Сохраняется ли длина? Ваши коды, похоже, предполагают, что длина осталась. Кроме того, я не вижу, чтобы вы обновляли какие-либо записи длины потока PDF, не говоря уже о перекрестных ссылках. Поскольку сжатый размер после ваших изменений, скорее всего, изменился, результат PDF, скорее всего, просто сломан. Кроме того, я не вижу, чтобы вы проверяли тип потока, который у вас есть. Если вы случайно измените потоки, содержащие изображения, они сами, вероятно, будут повреждены. Пожалуйста, предоставьте образец выходного PDF-файла вашего кода для проверки.
Ответ №1:
Ваше предположение, что текст можно найти буквально в потоке содержимого, неверно.
Предположим, что у вас есть PDF-файл с содержанием Hello World. Тогда у вас может быть поток, который выглядит следующим образом:
q
BT
36 806 Td
0 -18 Td
/F1 12 Tf
(Hello World!)Tj
0 0 Td
ET
Q
Но это также может выглядеть так:
Q
BT
/F1 12 Tf
88.66 367 Td
(ld) Tj
-22 0 Td
(Wor) Tj
-15.33 0 Td
(llo) Tj
-15.33 0 Td
(He) Tj
ET
q
Ваш код обнаружит слово «Hello» в первом потоке, но пропустит его во втором.
Программа просмотра PDF будет отображать оба потока точно так же: вы увидите «Hello World» в одной и той же позиции.
Иногда строки разбиваются на более мелкие части, часто встречаются текстовые массивы для введения кернинга и т. Д… Это все стандартная практика в PDF.
PDF — это не тот формат, который подходит для редактирования. Я не говорю, что это невозможно, но вам потребуется пара недель дополнительного программирования, если вы хотите удовлетворить свои требования о возможности замены одной строки другой в потоке PDF.
Комментарии:
1. И это без учета возможного изменения макета при замене «iii» на «mmm», поскольку «i» и «m» имеют разный размер символов.
2. Я понимаю это, но проблема заключается не в поиске текста, а в том, что когда я делаю inflate, а затем deflate, то какой-то текст отсутствует. Поэтому я думаю, что inflate не может распаковывать некоторые данные или сжимать их.
3. Может быть, вы искажаете XObjects, может быть, вы делаете неправильные предположения о фильтрах,… Трудно сказать. Загрузите iText RUPSИ сравните PDF-файлы. RUPSЭТО инструмент, который позволяет заглядывать внутрь PDF.
4. Я тестировал его с некоторыми PDF-файлами с сайта разработчика Apple, поэтому любые файлы документации оттуда не работают.
5. @BartoszBialecki Более точно: пожалуйста, предоставьте выходной PDF -файл, созданный вашим кодом, который вы считаете ошибочным. Извините, если это было непонятно.
Ответ №2:
В вашем коде есть несколько проблем, последствия которых видны в примере newpdf.pdf, который вы предоставили в комментарии к ответу Бруно:
-
После записи повторно сжатого потока в выходной файл вы добавляете » nendstr» и продолжаете размер этой строки, 7 символов, за пределами конца исходного потока во входном буфере, что, скорее всего, предотвратит просмотр «потока» в «endstream» в качестве начала исходного потока.следующий поток:
[self saveFile:(char *)"nendstr" len:7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]]; [...] buffer = streamend 7;
Проблема при добавлении этой строки заключается в том, что вы предполагаете, что «конечному потоку» во входном буфере предшествует ровно один байт НОВОЙ СТРОКИ (0x0A). Это предположение неверно, потому что
a. в PDF существует три типа допустимых маркеров конца строки: одиночный ПЕРЕВОД СТРОКИ (0x0A), одиночный ВОЗВРАТ КАРЕТКИ (0x0D) или пара ВОЗВРАТА КАРЕТКИ и ПЕРЕВОДА СТРОКИ (0x0D 0x0A), и любой из этих маркеров конца строки может предшествовать»конечный поток» во входном буфере; в приведенном выше коде, где вы вычисляете конец сжатого потока, вы игнорируете значение возврата одной КАРЕТКИ, а здесь вы игнорируете значение 2 байт; и, кроме того:
b. спецификация PDF даже не требует, а просто рекомендует добавить конец строки между концом потока и ключевым словом «endstream», см. Раздел 7.3.8.1:
После данных и перед конечным потоком должен быть маркер конца строки
Это уже прерывает первый поток в вашем примере файла, в котором в исходном файле нет маркера конца строки, и поэтому ваш результат заменяет исходный «endstream» на «nendstram». На самом деле это происходит довольно часто в вашем примере.
-
Вы полностью игнорируете, что поток PDF в своем словаре содержит запись, содержащую длину потока, см. Раздел 7.3.8.2 в спецификации PDF:
Каждый словарь потока должен иметь запись длины, которая указывает, сколько байт файла PDF используется для данных потока.
Ваши манипуляции, даже если вы только распаковываете и повторно сжимаете, вероятно, изменят длину сжатого потока. Таким образом, вам необходимо обновить эту запись длины. Это, по общему признанию, несколько усложняет вашу задачу, поскольку этот словарь находится перед потоком. Кроме того, в случаях, подобных вашему исходному файлу, эта запись может даже не содержать значение напрямую, а вместо этого ссылаться на косвенный объект где-то еще в файле.
Это прерывает второй поток в вашем файле, который утверждает, что он имеет длину 8150 байт, но вместо этого он на 200 байт длиннее. Любой просмотрщик PDF может предположить, что содержимое этого потока в вашем файле имеет длину всего 8150 байт, и, таким образом, игнорировать содержимое этих конечных 200 байт. Это вполне может быть причиной, по которой вы заметили, что
отсутствует некоторый текст или графика.
-
Вы полностью игнорируете, что в PDF есть таблица перекрестных ссылок или поток (или, возможно, даже цепочка из них), см. Раздел 7.5.4 в спецификации PDF:
Таблица перекрестных ссылок содержит информацию, которая разрешает произвольный доступ к косвенным объектам в файле, так что весь файл не нужно читать, чтобы найти какой-либо конкретный объект. Таблица должна содержать однострочную запись для каждого косвенного объекта, указывающую смещение в байтах этого объекта в теле файла. (Начиная с PDF 1.5, часть или вся информация о перекрестных ссылках может альтернативно содержаться в потоках перекрестных ссылок; см. 7.5.8, «Потоки перекрестных ссылок».)
Ваши манипуляции, даже если вы только распаковываете и повторно сжимаете, вероятно, изменят длину сжатого потока. Таким образом, вам необходимо обновить смещения всех следующих объектов в таблице перекрестных ссылок.
Поскольку размер второго потока в вашем файле результатов уже отличается, только очень немногие записи перекрестных ссылок в этом файле являются правильными.
-
Предполагается, что каждый поток PDF-файлов сдут. Это предположение неверно, см. таблицу 5 в спецификации PDF.
Ваш код по существу отбрасывает все потоки, которые он не может раздуть. Это также может быть причиной, по которой вы заметили, что
отсутствует некоторый текст или графика.
-
Вы предполагаете, что последовательность «stream» в PDF однозначно указывает на начало потока. Это неправильно, эта последовательность может быть легко использована и в других контекстах.
-
Вы предполагаете, что первая последовательность «endstream» в PDF после начала потока однозначно указывает на конец этого потока. Это неправильно, эта последовательность также может быть частью содержимого потока. Вы должны использовать значение записи длины в словаре потока.
Кроме того, вы, похоже, предполагаете, что каждый поток, который вы встречаете, все еще используется в результирующем PDF. Это не обязательно должно быть так. Особенно в случае дополнительных обновлений (см. Раздел 7.5.6 в спецификации PDF), в файле может быть много объектов, которые больше не используются. Хотя это не обязательно нарушает синтаксис результирующего файла, ваши изменения (если они зависят друг от друга) семантически неверны.
Комментарии:
1. Поздравляем с очень исчерпывающим ответом! 1
2. Большое вам спасибо за ваш ответ. Код, который я использую, я нашел на сайте codeproject и немного изменил, поэтому он может быть неправильным.
3. @BartoszBialecki Это определенно неправильно. На самом деле большинство быстрых и простых решений для анализа и обработки PDF-файлов не работают и работают только для небольшого подмножества всех PDF-файлов, если вообще работают. Первым показателем является то, использует ли инструмент перекрестные ссылки. Если этого не происходит, скорее всего, это мусор.
4. @mkl вы правы, я должен немного внимательнее изучить спецификацию pdf и исправить это. Еще раз спасибо.
Ответ №3:
я думаю, вам нужно прочитать о том, как текст хранится внутри файла PDF,
вот ссылка на спецификацию http://www.adobe.com/devnet/pdf/pdf_reference.html
текст раздела 9 — ключ к пониманию.