使用 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 合法值会触发该标记),因此由于该转换 可能 导致错误,因此它将始终被视为一个错误。
最终导致此错误的原因有两点:
- Java 只有一个无符号整数类型(
char
,对于那些在家玩的人来说)并且这个库选择使用 long
来表示一个无符号的 4 字节整数
- 这个特定的文件不符合规格并且使用
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 文件。
经验教训:
- 规范就是问题的答案"is my file correct."
- 很难编写像样的 TIFF 编码器或解码器。在编写自己的库之前考虑一个商业库(尽管在这个例子中,我们发现了两个商业库中的错误)。
- 生成文件时输入软件字符串,以便我们在出现问题时与您联系。
本课到此结束。
我是来自 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 年里一直这样做。
我在 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 合法值会触发该标记),因此由于该转换 可能 导致错误,因此它将始终被视为一个错误。
最终导致此错误的原因有两点:
- Java 只有一个无符号整数类型(
char
,对于那些在家玩的人来说)并且这个库选择使用long
来表示一个无符号的 4 字节整数 - 这个特定的文件不符合规格并且使用
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 文件。
经验教训:
- 规范就是问题的答案"is my file correct."
- 很难编写像样的 TIFF 编码器或解码器。在编写自己的库之前考虑一个商业库(尽管在这个例子中,我们发现了两个商业库中的错误)。
- 生成文件时输入软件字符串,以便我们在出现问题时与您联系。
本课到此结束。
我是来自 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 年里一直这样做。