【问题标题】:ClassCastException converting TIF to PDF using iTextClassCastException 使用 iText 将 TIF 转换为 PDF
【发布时间】:2015-05-11 15:07:10
【问题描述】:

我在 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)

【问题讨论】:

  • 可以在图像查看器中打开 TIFF 的事实并不总是意味着这样的 TIFF 是有效的。如果您想忽略 TIFF 中的错误(这是许多图像查看器所做的),请阅读问题的答案Exception when converting tiff file to pdf file with iText
  • 尝试了以下操作,替换了问题行,结果相同:Image tempImage=TiffImage.getTiffImage(myTiffFile, i, true);Image tempImage=Image.getInstance("C:\\local\\docs\\test.01.tif",true);
  • 那么这个特定的 TIFF 还有其他问题。它适用于其他 TIFF,不是吗?如果您希望解决此问题,则必须共享“坏”TIFF 以供检查。
  • 布鲁诺...感谢您的帮助!!!这是我的bad files之一的链接

标签: java pdf itext tiff


【解决方案1】:

我将深入研究您的文件的十六进制手术、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 

现在,查看调用堆栈并将其追溯到源代码,我看到正在进行调用以获取填充订单。 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,在这种情况下它会抛出 ClassCastException,因为这些情况下的类型常量分别是 1、7、6、3、8 和 9,并且标签的类型是4.

那为什么代码错了?

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

为什么没有这种情况?查看周围的代码,该库将 4 字节无符号整数视为 java 类型“long”,并尝试将 4 字节无符号整数视为 4 字节有符号整数可能导致符号位溢出(即使没有任何合法值因为这个标签会触发那个)所以因为那个演员可能会导致一个错误,它会一直被视为一个错误。

这个bug的最终原因是两件事:

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

此特定文件不是由研究生编写的。至少我不这么认为。

在这种情况下,这个问题很可能是由fCoder 引起的。我们知道这一点是因为他们将其放入软件字符串Created by fCoder Graphics Processor。我之所以叫他们出来,是因为他们用软件字符串来识别自己。这个错误(一个不正确的类型,可能是由于其源代码中的复制粘贴错误)虽然是一个小错误,但会导致问题,也许他们会解决这个问题。在我的世界中,排名第一的最优先级删除所有错误是“生成错误文件”。如果我这样做了,我肯定会想知道所以我可以修复我的代码。同时,iText 也应该更新他们的代码以能够接受此类文件。

经验教训:

  1. 规范是对“我的文件是否正确”问题的回答。
  2. 很难编写一个像样的 TIFF 编码器或解码器。在编写自己的库之前考虑一个商业库(尽管在此示例中,我们发现的不是一个而是两个商业库中的错误)。
  3. 在您生成文件时输入软件字符串,以便我们在出现问题时与您联系。

课程到此结束。

【讨论】:

  • 很好的答案。我刚刚在我们的内部问题跟踪器中看到我们的一位工程师对它的引用。我们将在 iText 中解决这个问题。感觉就像我们多年来一直在解决 TIFF 问题。
  • 坏消息,@Bruno,但您将永远解决 TIFF 问题。或者好消息,因为你将永远有工作。 See also
【解决方案2】:

这是来自 fCoder 的 Mikhael Bolgov。

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

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

请注意,它被命名为 fCoder Graphic 处理器。我们曾经 这样写到 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 年里一直这样做。

【讨论】:

  • 我从一个客户端接收文件,该客户端有一些生成 TIFF 文件的扫描软件。很明显,他们使用的扫描软件存在问题。我的解决方案是通过 Imaging.getBufferedImage(file) 通过 Apache Commons Image 库复制坏文件。再次感谢您的协助
猜你喜欢
  • 2016-05-14
  • 2020-09-04
相关资源
最近更新 更多