Почему квадраты показаны вместо символов в выходном файле с использованием pdfbox - PullRequest
0 голосов
/ 02 ноября 2018

Потерял неделю на поиск решения, но все равно не смог. Может быть, кто-то знает: я пытаюсь заменить токен, например, @test на номера 123456 в файле .pdf, используя pdfbox.

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

Примечание: мы предполагаем, что это проблема с портом и тест на сборке Java в v 2.0 и сталкиваются с той же проблемой.

Может, кто-нибудь сталкивался с подобной проблемой и знает решение?

Технические данные:

  • Версия: PDFBox.NET-1.8.9, взята из http://www.squarepdf.net/pdfbox-in-net
  • Язык: C #
  • .NET Framework 4.5.2
  • Используемые шрифты: Times New Roman, Tahoma, Courier, calibri.

Создание MS Word:

  • Просто щелкните правой кнопкой мыши на рабочем столе
  • Выберите документ Microsoft Word из создания новой точки
  • Печать внутри текста: @ test

Сценарий:

private void ReplaceTextInPdf(string inputPath, string outputPath) {
            PDDocument doc = null;
            try {
                File input = new File(inputPath);
                doc = PDDocument.loadNonSeq(input, null);
                List pages = doc.getDocumentCatalog().getAllPages();

                for (int i = 0; i < pages.size(); i++) {
                    PDPage page = (PDPage)pages.get(i);
                    PDStream contents = page.getContents();
                    PDFStreamParser parser = new PDFStreamParser(contents.getStream());
                    parser.parse();
                    List tokens = parser.getTokens();

                    for (int j = 0; j < tokens.size(); j++) {
                        Object next = tokens.get(j);
                        if (next is PDFOperator) {
                            PDFOperator op = (PDFOperator)next;
                            //Tj and TJ are the two operators that display
                            //strings in a PDF
                            if (op.getOperation() == "Tj") {
                                //Tj takes one operator and that is the string
                                //to display so lets update that operator
                                COSString previous = (COSString)tokens.get(j - 1);
                                String tempString = previous.getString();

                                tempString = tempString.replace("@test", "123456");

                                previous.reset();
                                previous.append(tempString.getBytes());
                            } else if (op.getOperation() == "TJ") {
                                String tempString = "";
                                COSString cosString = null;
                                COSArray previous = (COSArray)tokens.get(j - 1);
                                for (int k = 0; k < previous.size(); k++) {
                                    Object arrElement = previous.getObject(k);
                                    if (arrElement is COSString) {
                                        cosString = (COSString)arrElement;
                                        tempString += cosString.getString();
                                        cosString.reset();
                                    }
                                }

                                if (tempString != null && tempString.trim().length() > 0) {

                                    tempString = tempString.replace("@test", "123456");

                                    for (int k = 0; k < previous.size(); k++) {
                                        Object arrElement = previous.getObject(k);
                                        if (arrElement is COSString) {
                                            cosString.reset();
                                            cosString.append(tempString.getBytes("ISO-8859-1"));
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    //now that the tokens are updated we will replace the
                    //page content stream.
                    PDStream updatedStream = new PDStream(doc);
                    OutputStream out1 = updatedStream.createOutputStream();
                    ContentStreamWriter tokenWriter = new ContentStreamWriter(out1);
                    tokenWriter.writeTokens(tokens);
                    page.setContents(updatedStream);
                }

                doc.save(outputPath);
            } finally {
                if (doc != null) {
                    doc.close();
                }
            }
        }

1 Ответ

0 голосов
/ 02 ноября 2018

В общем

Прежде всего, код, который вы используете, работает только при благоприятных обстоятельствах, то есть только для PDF-файлов, созданных особым образом. В то время как PDF-файлы в прежние годы довольно часто создавались таким образом, в настоящее время их в основном уже нет. Это привело к удалению примера PDFBox, из которого этот код был получен из базы исходного кода PDFBox 2.0.

Соответствующая запись в руководстве по миграции объясняет:

Почему был удален пример ReplaceText?

Пример ReplaceText был удален, поскольку он дал неверную иллюзию того, что текст можно легко заменить. Слова часто разделяются, как видно из этого фрагмента потока контента:

[ (Do) -29 (c) -1 (umen) 30 (tation) ] TJ

Другие проблемы появятся с подмножествами шрифтов: например, если используются только глифы для a, b и c, они будут закодированы как шестнадцатеричные 0, 1 и 2, поэтому вы не найдете «abc». Кроме того, вы не можете заменить «c» на «d», поскольку он не является частью подмножества.

У вас также могут быть проблемы с лигатурами, например, «Ff», «fl», «fi», «ffi», «ffl», которые могут быть представлены одним кодом во многих шрифтах. Чтобы понять это самостоятельно, просмотрите любой файл с помощью PDFDebugger и просмотрите запись «Содержание» на странице.

См. Также PDFBox 2.0 RC3 - ​​Найти и заменить текст

( Миграция в PDFBox 2.0.0 )

Проблема, связанная с разделением слов для кернинга, в основном была обойдена в вашем коде путем конкатенации блоков строковых параметров для оператора TJ . Остающиеся проблемы остаются, тем не менее.

В случае ваших примеров документов

В случае вашего примера документа проблема заключается в том, что заменяющие "цифры показывают друг на друга":

screen shot original ==> screen shot with replacement

Причина аналогична проблеме «наборов шрифтов», упомянутой в руководстве по миграции. Тем не менее, рассматриваемая программа шрифтов TTF не встроена, так что это не настоящая проблема "подмножества шрифтов" Но информация, относящаяся к шрифту, хранящаяся в PDF, является правильной только для глифов, фактически используемых в исходном PDF, то есть '@', 'e', ​​'s' и 't', но не для глифов замены, то есть цифр «1», хотя «6».

Специфичной для глифа информацией, имеющей отношение к данному случаю, является ширина глифа: только для первоначально используемых глифов она задана правильно, для всех других глифов данная ширина равна 0! Следствие: после рисования одного из ваших замещающих глифов позиция для рисования следующего глифа не смещается надлежащим образом, но остается той же самой (как уместно для глифов шириной 0), поэтому следующий нарисованный глиф начинается в той же позиции, эффективно рисуя все ваши заменяющие глифы друг на друга.

(Более конкретно, массив widths для этого шрифта выглядит следующим образом:

[ 250 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 921 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 444 0 0 0 0 0 0 0 0 0 0 0 0 0 389 278] 

с кодированием '@', 'e', ​​'s' и 't' с использованием WinAnsiEncoding и шрифта, состоящего из диапазона от '@' до 't'.)

В этом особом случае вы, вероятно, сможете решить проблему, невидимо (например, белым по белому) напечатав в шаблоне Word строку со всеми символами шрифта, которые вы, возможно, захотите использовать в качестве замены для заполнителя.

В целом, однако, кодировка не должна быть чем-то вроде ASCII'а, подобной WinAnsiEncoding , но вместо этого может быть совершенно другой, вероятно, даже подстроенной по этому случаю, например. # 1 для первого глифа, используемого на странице, # 2 для второго, другого глифа на этой странице и т. Д. Таким образом, в общем, обходной путь не так легко найти.

...