ClassCastException使用iText将TIF转换为PDF

Tim*_*-ah 2 java pdf tiff itext

我在Windows 7上使用带有Java 7(1.7.0_71)64位的iText版本5.5.6(也测试了5.3.4)

这是一个示例代码

@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();
        }
}
Run Code Online (Sandbox Code Playgroud)

生成以下错误

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)
Run Code Online (Sandbox Code Playgroud)

pli*_*nth 6

我将对您的文件深入研究十六进制手术,iText中异常的原因以及最终导致此错误的原因.然后,我将继续描述为什么会发生这种情况.

您的文件是结构化的,以便主IFD位于文件的末尾.这是文件头:

49 49 2A 00 96 6C 00 00 
intel magic offset-----
Run Code Online (Sandbox Code Playgroud)

其中说"我是英特尔(小端)字节顺序的TIFF,我的主要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 
Run Code Online (Sandbox Code Playgroud)

现在,查看调用堆栈并将其追溯到源代码,我看到正在调用以获取填充顺序.1位文件的填充顺序描述了字节中的高位或低位是否在显示中最左边.

TIFFField fillOrderField =  dir.getField(TIFFConstants.TIFFTAG_FILLORDER);
if (fillOrderField != null)
    fillOrder = fillOrderField.getAsInt(0);
Run Code Online (Sandbox Code Playgroud)

我们知道这会被调用,因为IFD中有一个填充顺序标记,它是一个值为1的4字节整数.

对你来说不幸的是,那次呼叫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();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以看到,如果类型不匹配,它可以抛出ClassCastException,在这种情况下,它将分别为1,7,6,3,8和9中的那些类型常量,并且标记的类型为4.

那为什么代码错了呢?

TIFF标签的问题在于,尽管规范很清楚FillOrder标签(10a)应该是无符号短(类型3),但文件中的标签是无符号的4字节int(类型4),但是那里的switch语句没有考虑到这一点(没有TIFF_LONG的情况).

为什么没有这个呢?查看周围的代码,这个库将4字节无符号整数视为java类型'long',并尝试将4字节unsigned int视为4字节signed int可能导致溢出到符号位(即使没有合法值对于此标记会触发)因此,由于该强制转换可能会导致错误,因此将始终视为一个错误.

最终导致此错误的原因有两个:

  1. Java只有一个无符号整数类型(char对于那些在家里玩的人),这个库选择long用来表示无符号的4字节int.
  2. 此特定文件超出规范并用于unsigned int此标记

或者更具体地说,所选Java类型与此TIFF文件之间存在阻抗不匹配.此字段代码尝试类型强.调用代码试图接受各种类型.它错过了这一个案例.

我查看了我自己的grins标签代码,看它是否会受到这个特殊问题的影响.答案是否定的,因为我的getIntValue()版本会让你溢出到符号位,如果这是你想要做的.

所以真正的解决方法是将代码更改为:

TIFFField fillOrderField =  dir.getField(TIFFConstants.TIFFTAG_FILLORDER);
if (fillOrderField != null)
    fillOrder = (int)fillOrderField.getAsLong(0);
Run Code Online (Sandbox Code Playgroud)

或者,对您的文件执行HEX手术,并将填充订单标签的数据类型更改为unsigned short.这最终是一个糟糕的解决方案,因为消费代码仍然容易受到错误的TIFF文件的影响.


无偿熨平板

我在过去10年中使用TIFF文件所学到的一件事是,不缺少破坏的TIFF文件,也​​不缺少没有阅读规范或未能正确实现新文件的工程师(偶尔,我一直是那个工程师.其中一些是毕业学生现在需要TIFF输出并编写一个快速和脏(破碎)的编码器,当IrfanView可以打开他们的输出时他们认为是正确的(这是一个无效的测试,因为IrfanView,我的TIFF编解码器也是,打开各种根本破裂的TIFF).

TIFF规范是看似直截了当.我这样说是因为格式本身感觉应该相对容易生成.标签是合乎逻辑的,IFD是标签的简单集合,指针标签可能很棘手,但是可管理.所发生的是,编写的代码缺少一定程度的抽象,这会阻止可能会漏掉的错误类.

这个特殊的文件不是由研究生写的.至少我不这么认为.

在这种情况下,这个问题可能是由fCoder引起的.我们知道这一点,因为他们将其放入软件字符串中Created by fCoder Graphics Processor.我打电话给他们是因为他们把软件字符串识别出来.这个错误(不正确的类型,可能是由于源代码中的复制粘贴错误),而一个小错误,导致问题,也许他们会解决这个问题.在我的世界中,#1 top-priority-drop-everything错误是"生成一个糟糕的文件".如果我这样做了,我肯定会想知道所以我可以修复我的代码.同时,iText还应更新其代码以便能够接受此类文件.

得到教训:

  1. 规范是"我的文件正确"这个问题的答案.
  2. 编写一个像样的TIFF编码器或解码器很难.在编写自己的库之前考虑一个商业库(虽然在这个例子中,我们发现了不只是两个商业库中的错误).
  3. 生成文件时输入软件字符串,以便我们在出现问题时与您联系.

这里吸取了教训.