ClassCastException конвертирует TIF в PDF с помощью iText

Я использую iText версии 5.5.6 (проверено также 5.3.4) с 64-битной Java 7 (1.7.0_71) в Windows 7.

Вот пример кода

@Test
public void testConvert() throws Exception {
        try{
            //Read the Tiff File
            RandomAccessFileOrArray myTiffFile=new RandomAccessFileOrArray("C:\\local\\docs\\test.01.tif");
            //Find number of images in Tiff file
            int numberOfPages= TiffImage.getNumberOfPages(myTiffFile);
            System.out.println("Number of Images in Tiff File: " + numberOfPages);
            Document TifftoPDF=new Document();
            PdfWriter.getInstance(TifftoPDF, new FileOutputStream("C:\\local\\docs\\test.01.pdf"));
            TifftoPDF.open();
            //Run a for loop to extract images from Tiff file
            //into a Image object and add to PDF recursively
            for(int i=1;i<=numberOfPages;i++){
                //*******           
                //******* this next line is generating the error
                //*******
                Image tempImage=TiffImage.getTiffImage(myTiffFile, i);
                TifftoPDF.add(tempImage);
            }
            TifftoPDF.close();
            System.out.println("Tiff to PDF Conversion in Java Completed" );
        }
        catch (Exception i1){
            i1.printStackTrace();
        }
}

выдает следующую ошибку

java.lang.ClassCastException
    at com.itextpdf.text.pdf.codec.TIFFField.getAsInt(TIFFField.java:315)
    at com.itextpdf.text.pdf.codec.TiffImage.getTiffImage(TiffImage.java:163)
    at com.itextpdf.text.pdf.codec.TiffImage.getTiffImage(TiffImage.java:315)
    at com.itextpdf.text.pdf.codec.TiffImage.getTiffImage(TiffImage.java:303)
    at com.pdf.ImageConverterImplIT.testConvert(ImageConverterImplIT.java:116)

person Timm-ah    schedule 11.05.2015    source источник
comment
Тот факт, что вы можете открыть TIFF в средстве просмотра изображений, не всегда означает, что такой TIFF действителен. Если вы хотите игнорировать ошибки в TIFF (что делают многие средства просмотра изображений), прочитайте ответ на вопрос Исключение при преобразовании файла tiff в файл pdf с помощью iText   -  person Bruno Lowagie    schedule 11.05.2015
comment
попробовал следующее, заменив проблемную строку, с теми же результатами: Image tempImage=TiffImage.getTiffImage(myTiffFile, i, true); и Image tempImage=Image.getInstance("C:\\local\\docs\\test.01.tif",true);   -  person Timm-ah    schedule 11.05.2015
comment
Тогда с этим конкретным TIFF что-то не так. Он работает с другими TIFF, не так ли? Если вы хотите, чтобы это было исправлено, вам придется поделиться плохим TIFF для проверки.   -  person Bruno Lowagie    schedule 12.05.2015
comment
Бруно... спасибо за помощь!!! Вот ссылка на один из моих плохих файлов   -  person Timm-ah    schedule 12.05.2015


Ответы (2)


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

Ваш файл структурирован так, что основной IFD находится в конце файла. Вот заголовок файла:

49 49 2A 00 96 6C 00 00 
intel magic offset-----

В котором говорится: «Я TIFF в порядке байтов Intel (с прямым порядком байтов), и мой основной IFD начинается со смещения 0x6c9c.

Если вы пропустите это место, вы увидите это:

0F 00 <- this is the total number of tags, each tag is 12 bytes

#  |  ID |Type | Count     | Value     |
01. 00 01 04 00 01 00 00 00 A2 06 00 00 width = 6a2
02. 01 01 04 00 01 00 00 00 4A 04 00 00 height = 44a
03. 02 01 03 00 01 00 00 00 01 00 00 00 bits per sample = 1
04. 03 01 03 00 01 00 00 00 04 00 00 00 Compression = CCITT G4
05. 06 01 03 00 01 00 00 00 00 00 00 00 Photometric = min is white
06. 0A 01 04 00 01 00 00 00 01 00 00 00 Fill order = msb to lsb
07. 11 01 04 00 01 00 00 00 08 00 00 00 Offset of strips = 8
08. 15 01 03 00 01 00 00 00 01 00 00 00 Samples per pixel = 4
09. 16 01 04 00 01 00 00 00 4A 04 00 00 Rows per strip = 448
0a. 17 01 04 00 01 00 00 00 5B 6C 00 00 Strip byte counts = 6c5b
0b. 1A 01 05 00 01 00 00 00 63 6C 00 00 Offset to x resolution = 6c63
0c. 1B 01 05 00 01 00 00 00 6B 6C 00 00 Offset to y resolution = 6c6b
0d. 1C 01 03 00 01 00 00 00 01 00 00 00 Planar Config = Contiguous
0e. 28 01 03 00 01 00 00 00 02 00 00 00 Resolution unit = inches
0f. 31 01 02 00 23 00 00 00 73 6C 00 00 Software string offset = 6c73
Location of next IFD, 0 means no more
00 00 00 00 

Теперь, глядя на стек вызовов и прослеживая его до источника, я вижу, что вызов выполняется для получения порядка заполнения. Порядок заполнения для 1-битных файлов описывает, является ли бит старшего или младшего разряда в байте самым левым на дисплее.

TIFFField fillOrderField =  dir.getField(TIFFConstants.TIFFTAG_FILLORDER);
if (fillOrderField != null)
    fillOrder = fillOrderField.getAsInt(0);

Мы знаем, что это будет вызвано, так как в вашем IFD есть тег порядка заполнения, который представляет собой 4-байтовое целое число со значением 1.

К сожалению для вас, этот вызов TIFFFIELD.getAsInt(0) вызывает сбой.

Если вы посмотрите на этот код:

public int getAsInt(int index) {
    switch (type) {
    case TIFF_BYTE: case TIFF_UNDEFINED:
        return ((byte[])data)[index] & 0xff;
    case TIFF_SBYTE:
        return ((byte[])data)[index];
    case TIFF_SHORT:
        return ((char[])data)[index] & 0xffff;
    case TIFF_SSHORT:
        return ((short[])data)[index];
    case TIFF_SLONG:
        return ((int[])data)[index];
    default:
        throw new ClassCastException();
    }
}

Вы можете видеть, что он может генерировать исключение ClassCastException, если тип не соответствует, и в этом случае это произойдет, поскольку эти константы типа в случаях равны 1, 7, 6, 3, 8 и 9 соответственно, а тип тега равен 4.

Так почему код неверный?

Проблема с тегами TIFF заключается в том, что, несмотря на то, что в спецификации довольно четко указано, что тег FillOrder (10a) должен быть беззнаковым коротким (тип 3), тег в вашем файле представляет собой беззнаковое 4-байтовое целое число (тип 4), но оператор switch не учитывает это (для TIFF_LONG нет случая).

Почему для этого нет дела? Глядя на окружающий код, эта библиотека обрабатывает 4-байтовые целые числа без знака как тип java «long», и попытка обрабатывать 4-байтовое целое число без знака как 4-байтовое целое число со знаком может привести к переполнению знакового бита (даже если ни одно из допустимых значений поскольку этот тег вызовет это), поэтому, поскольку это приведение может вызвать ошибку, оно всегда будет рассматриваться как одно.

В конечном счете причиной этой ошибки являются две вещи:

  1. Java имеет только один беззнаковый целочисленный тип (char, для тех из вас, кто играет дома), и эта библиотека решила использовать long для представления беззнакового 4-байтового целого числа.
  2. Этот конкретный файл не соответствует спецификации и использует unsigned int для этого тега.

Или, точнее, существует несоответствие импеданса между выбранными типами Java и этим файлом TIFF. Этот код поля пытается быть типоустойчивым. Вызывающий код пытается принимать самые разные типы. Он пропустил этот случай.

Я посмотрел на свой собственный код тега для ухмылки, чтобы увидеть, будет ли он страдать от этой конкретной проблемы. Ответ - нет, так как моя версия getIntValue() позволит вам переполниться битом знака, если это то, что вы хотите сделать.

Таким образом, реальное исправление состоит в том, чтобы изменить код на:

TIFFField fillOrderField =  dir.getField(TIFFConstants.TIFFTAG_FILLORDER);
if (fillOrderField != null)
    fillOrder = (int)fillOrderField.getAsLong(0);

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


Бесплатная стяжка

Одна вещь, которую я усвоил за последние 10 лет работы с файлами TIFF, заключается в том, что нет недостатка в сломанных файлах TIFF и нет недостатка в инженерах, которые либо не читали спецификацию, либо не смогли правильно ее реализовать, создав новые поврежденные файлы ( и время от времени я был этим инженером). Некоторые из них являются аспирантами, которым нужен вывод TIFF ПРЯМО СЕЙЧАС, и они пишут быстрый и грязный (сломанный) кодировщик, который они считают правильным, когда IrfanView может открыть их вывод (что является недопустимым тестом, поскольку IrfanView, а также мой кодек TIFF, открывает множество принципиально испорченных файлов TIFF).

спецификация TIFF обманчиво проста. Я говорю это потому, что сам формат кажется, что его относительно легко создать. Теги логичны, IFD — это простые наборы тегов, теги-указатели могут быть сложными, но ими можно управлять. Что происходит, так это то, что написан код, которому не хватает уровня абстракции, который предотвратил бы классы ошибок, которые в противном случае проскользнули бы.

Этот конкретный файл не был написан аспирантом. По крайней мере, я так не думаю.

В данном случае проблема, скорее всего, была вызвана fCoder. Мы знаем это, потому что они поместили это в строку программного обеспечения Created by fCoder Graphics Processor. Я вызываю их, потому что они используют программную строку, чтобы идентифицировать себя. Эта ошибка (неправильный тип, вероятно, из-за ошибки копирования-вставки в их источнике), хотя и незначительная ошибка, вызывает проблемы, и, возможно, они это исправят. В моем мире первоочередная ошибка № 1 при удалении всего — это «создание плохого файла». и если бы я сделал это, я, черт возьми, хотел бы знать, чтобы исправить свой код. Между тем, iText также должен обновить свой код, чтобы иметь возможность принимать этот класс файлов.

Уроки выучены:

  1. Спецификация — это ответ на вопрос «правилен ли мой файл».
  2. Трудно написать приличный кодировщик или декодер TIFF. Рассмотрите коммерческую библиотеку, прежде чем писать свою (хотя в этом примере мы нашли ошибки не в одной, а в двух коммерческих библиотеках).
  3. Введите строку программного обеспечения при создании файла, чтобы мы могли связаться с вами в случае возникновения проблемы.

Вот и конец урока.

person plinth    schedule 12.05.2015
comment
Отличный ответ. Я только что увидел упоминание об этом от одного из наших инженеров в нашем внутреннем трекере проблем. Мы исправим это в iText. Такое ощущение, что мы решали проблемы с TIFF целую вечность. - person Bruno Lowagie; 22.06.2015
comment
Плохие новости, @Bruno, но ты будешь вечно исправлять проблемы с TIFF. Или хорошие новости, потому что у вас будет работа навсегда. см. также - person plinth; 22.06.2015

Это Михаил Болгов из fCoder.

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

0131.H Software               ASCII 35 "Created by fCoder Graphic Processor"

Обратите внимание, что он называется fCoder Graphic Processor. Мы так писали примерно до 2005-2006 года. В новых версиях это «Графический процессор fCoder».

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

Вот пример файла, созданного с помощью последней версии нашего 2TIFF, которое работает на последней версии нашего процессора:

Header
Byte order = Littleendian
Version  = 2A.H, TIFF 6.0
First IFD = 8.H
End of header

[Root IFD] 00000008.H
00FE.H New subfile type                      LONG 1 (0.H) [Full
resolution image]
0100.H Image width                           LONG 1 280
0101.H Image height                          LONG 1 560
0102.H Bits per sample                       SHORT 1 1
0103.H Compression                           SHORT 1 (0004.H) CCITT
Group 4/ T.6/ MMR
0106.H Photometric interpretation            SHORT 1 Black is zero
010A.H Fill order                            SHORT 1 1
0111.H Strip offsets                         LONG 3 [206, 16804, 35502]
0115.H Samples per pixel                     SHORT 1 1
0116.H Rows per strip                        LONG 1 234
0117.H Strip byte counts                     LONG 3 [16598, 18698, 3915]
011A.H X resolution                          RATIONAL 1 96 (96 / 1 = 96)
011B.H Y resolution                          RATIONAL 1 96 (96 / 1 = 96)
011C.H Planar configuration                  SHORT 1 Single plane
0128.H Resolution unit                       SHORT 1 Inch
0131.H Software                              ASCII 37 "Created by
fCoder Graphics Processor"
[Next IFD] 00000000.H

Root pages = 1
Total pages = 1

Итак, еще раз. Новые версии нашего графического процессора создают правильные файлы TIFF. И скорее всего делают это последние 10 лет.

person Mikhael Bolgov    schedule 28.10.2015
comment
Я получал файл от клиента, у которого было программное обеспечение для сканирования, генерирующее файлы TIFF. Очевидно, проблема в программном обеспечении для сканирования, которое они используют. Мое решение состояло в том, чтобы просто скопировать плохие файлы через библиотеку изображений Apache Commons через Imaging.getBufferedImage(file). Еще раз спасибо за помощь - person Timm-ah; 29.10.2015