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)
我将对您的文件深入研究十六进制手术,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可能导致溢出到符号位(即使没有合法值对于此标记会触发)因此,由于该强制转换可能会导致错误,因此将始终视为一个错误.
最终导致此错误的原因有两个:
char对于那些在家里玩的人),这个库选择long用来表示无符号的4字节int.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还应更新其代码以便能够接受此类文件.
得到教训:
这里吸取了教训.