使用 iText 将 TIF 转换为 PDF 的 ClassCastException

ClassCastException converting TIF to PDF using 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();
        }
}

生成以下错误

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)

我将深入研究您的文件的十六进制手术,iText 中异常的原因以及最终导致此错误的原因。然后我会继续写一篇文章来描述为什么会这样。

您的文件结构使得主 IFD 位于文件末尾。这是文件头:

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

这表示“我是 Intel(小端)字节顺序的 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 

现在,查看调用堆栈并将其追溯回源,我看到正在调用以获取填充顺序。 Fill order for 1 bit files描述了一个字节中的高位或低位在显示的最左边。

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

我们知道这将被调用,因为在您的 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();
    }
}

您可以看到,如果类型不匹配,它会抛出 ClassCastException,在这种情况下,它会抛出,因为这些情况下的类型常量分别是 1、7、6、3、8 和 9,而标签的类型是 4.

为什么代码是错误的?

TIFF 标签的问题在于,尽管规范非常清楚 FillOrder 标签 (10a) 应该是无符号短整型(类型 3),但您文件中的标签是无符号 4 字节整数(类型 4),但那里的 switch 语句没有说明这一点(TIFF_LONG 没有案例)。

为什么没有这个案例?查看周围的代码,该库将 4 字节无符号整数视为 java 类型 'long' 并尝试将 4 字节无符号整数视为 4 字节有符号整数可能会导致符号位溢出(甚至尽管此标记的 none 合法值会触发该标记),因此由于该转换 可能 导致错误,因此它将始终被视为一个错误。

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

  1. Java 只有一个无符号整数类型(char,对于那些在家玩的人来说)并且这个库选择使用 long 来表示一个无符号的 4 字节整数
  2. 这个特定的文件不符合规格并且使用 unsigned int 作为这个标签

或者更具体地说,所选 java 类型与此 TIFF 文件之间存在阻抗不匹配。此域代码试图成为强类型。调用代码是 尝试 接受多种类型。它漏掉了这一个案例。

我查看了自己的 grins 标签代码,看看它是否会遇到这个特定问题。答案是否定的,因为我的 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 specification 看似直截了当。我这么说是因为格式本身感觉应该相对容易生成。标签是合乎逻辑的,IFD 是标签的简单集合,指针标签可能很棘手,但易于管理。发生的情况是编写的代码缺乏抽象级别,这将防止 classes 错误,否则会漏掉。

这个文件不是研究生写的。至少我不这么认为。

在这种情况下,这个问题很可能是由 fCoder 引起的。我们知道这一点,因为他们将其放入软件字符串 Created by fCoder Graphics Processor。我叫他们出来是因为他们用软件字符串来标识自己。这个错误(不正确的类型,可能是由于源代码中的复制粘贴错误)虽然是一个小错误,但会引起问题,也许他们会修复它。在我的世界里,#1 最优先删除所有错误是 "generates a bad file.",如果我这样做了,我肯定会想知道,这样我就可以修复我的代码。同时,iText 也应该更新他们的代码,以便能够接受这个 class 文件。

经验教训:

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

本课到此结束。

我是来自 fCoder 的 Mikhael Bolgov。

我们检查了其中一个链接的错误文件 第一条消息。它的结构中有一行:

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

请注意,它的名称是 fCoder Graphic Processor。我们曾经 这样写到2005-2006年左右。在较新的版本中,它是“fCoder 显卡处理器。

所以我们的处理器可能创建了这个错误的文件。但它 将是一个非常非常旧的版本。

这是使用我们的最新版本创建的 example of a file 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 年里一直这样做。