PDF文档解析
Quartz提供了让你检查PDF文档结构和内容流(contentstream)的函数.检查文档结构可以让你读取文档目录的条目和与每个条目相关的内容。通过递归地遍历目录,您可以检查整个文档。
一个PDF的内容流(contentstream)正如其名字所暗示的—一个连续的数据流 例如'BT 12 /F71 Tf (draw thistext) Tj . . . '此处PDF操作符以及他们的描述符都混有实际的PDF内容。检查内容流,你需要按顺序访问它。
本章揭示了如何查看PDF文档的结构和解析一个PDF文件的内容。
检查PDF文档结构
PDF文件可能包含多个页面的图像和文本。您可以使用Quartz访问文档和页面级别的元数据以及PDF页上的对象。本节提供了一个非常简短的介绍,关于您可以访问的元数据。
一个PDF文档对象 (CGPDFDocument)包含了所有的信息,涉及到一个PDF文档,包括它的目录和内容。目录中的条目的递归地描述了PDF文档的内容。你可以访问一个PDF文档的内容通过调用函数CGPDFDocumentGetCatalog。
一个PDF页面对象(CGPDFPage)代表PDF文档中的一页且包含此特定的页面所有信息,包括页面字典和页面内容。您可以获得一个页面字典通过调用该函数CGPDFPageGetDictionary。
图 14-1 展示了描述了两张图片的元数据—组成了图13-2的PDF文件。
你可以通过访问PDF的元数据获得更多有用的信息。图14-1只是一个示例。例如,您可以通过使用图14-1中的代码检查一个PDF是否有缩略图(见图14-2)。
清单 14-1 得到PDF的缩略视图
CGPDFDictionaryRef d; |
CGPDFStreamRef stream; // represents a sequence of bytes |
d = CGPDFPageGetDictionary(page); |
// check for thumbnail data |
if (CGPDFDictionaryGetStream |
// get the data if it exists |
data = CGPDFStreamCopyData (stream, &format); |
Quartz为你执行了所有数据流的解密和解密工作。
图 14-2 缩略图片
Quartz提供了很多的功能,您可以使用它来获得PDF的元数据中对应项的指定值。例如,您使用函数CGPDFObjectGetValue,传入一个CGPDFObjectRef,和一个PDF对象类型(kCGPDFObjectTypeBoolean,kCGPDFObjectTypeInteger, 等等),以及一个存储值的存储空间。返回时,此存储空间就被得到的值填充了。
还有很多其他的函数可以用来遍历PDF文件的层次结构来访问各个节点和他们的子节点。例如,CGPDFArray函数(CGPDFArrayGetBoolean, CGPDFArrayGet Dictionary, CGPDFArrayGetInteger,等等) 允许您访问数组的值,以检索特定类型的值。你可以通过阅读阅读PDF规范来找到更多关于如何使用这些函数。
解析PDF内容
PDF内容流包含操作符,表明一个PDF内容流的一部分对你的应用程序来说是感兴趣的(解析时必须用到的???好搓)。一个操作符也标记一个单独的点或一个序列。一个操作符被指定为一个标签,此标签拥有一个属性列表或与它相关联的对象。一个标签指定了点或内容序列代表了什么。一个属性列表是一本字典,其中包含PDF内容创造者指定的键-值对。当您解析一个PDF内容流时,您的应用程序查找感兴趣的任何标记,检查例如 标签、属性列表,或对象相关联的标记,然后执行任何进一步的处理,是合适的。参考PDF引用来查看PDF操作符的完整列表。
你使用CGPDFScanner对象(CGPDFScannerRef数据类型)来解析一个PDF内容流。CGPDFScanner对象调用在流中注册了回调方法的任何操作符的回调方法。
你可以执行以下部分所描述的任务来解析内容流:
1.“为操作符写回调方法”你需要为你要操作的操作符编写回调方法。
2.“创建和设置操作表”。
3.“打开PDF文档”。
4.“扫描每页的内容流”。
当这么做的时候,你应该确保释放了 the scanner, content stream,and operator table。
下面的部分展示如何解析内容流找到标记-内容操作符(marked-content operators)(见表14-1)。标记的内容操作符只代表部分用于PDF内容的PDF操作符。当您编写自己的代码时,你最好寻找适合您的应用程序的PDF操作符。
表 14-1 标注的内容操作符代表一些你可以解析的PDF操作符
Operator | Description |
MP | 有一个关联标签的标记点。 |
DP | 有一个标签和一个属性列表或对象与它相关联的标记点。 |
BMC | 标志着一个标记内容序列的开始(开始标记内容)和标志一个内容序列结尾的EMC 标记是配对的。有一个相关联的标签。 |
BDC | 标志着一个标记内容序列的开始和标志一个内容序列结尾的EMC 标记是配对的。有一个标签和一个属性列表或对象与之相关联。 |
EMC | 标志着一个以BMC或BDC标记开始的标记内容序列的结束(结束标记内容)。这个操作符没有任何与之关联的标签。 |
为操作符编写回调方法
当Quartz调用你的PDF操作符的回调方法时,它通过你的回调方法传递了一个CGPDFScanner对象和一个指向任何必要信息的指针。通常,你的回调方法检索任何与操作符相关联的项。例如,在14-2所展示的清单中对应于MP操作符的回调方法调用了CGPDFScannerPopName函数从堆栈中检索与操作符相关联的字符串。如果清单中的代码成功的从scanner栈中检索出name,就打印出来。
Quartz有各式各样的CGPDFScannerPop函数用于检索objects,Boolean values, names, numbers, strings, arrays, dictionaries, and streams.每个函数都返回一个布尔值表明是否成功检索了该项。
清单14-2 MP操作符的回调方法
static void |
op_MP (CGPDFScannerRef s, void *info) |
{ |
const char *name; |
|
if (!CGPDFScannerPopName(s, &name)) |
return; |
|
printf("MP /%s\n", name); |
} |
创建并设置操作表
一个CGPDFOperatorTable对象存储了你编写的PDF操作符回调函数。函数CGPDFOperatorTableCreate
代码清单14-3为表14-1中列出的每个标记-文本操作符 设置了一个回调方法。你的应用应该为你感兴趣的那些操作符设置回调方法。PDF操作符字符串由Adobe PDF Reference 制定。
清单 14-3 为操作表设置回调方法
CGPDFOperatorTableRef myTable; |
|
myTable = CGPDFOperatorTableCreate |
|
CGPDFOperatorTableSetCal |
CGPDFOperatorTableSetCal |
CGPDFOperatorTableSetCal |
CGPDFOperatorTableSetCal |
CGPDFOperatorTableSetCal |
打开PDF文档
在你扫描PDF文档之前,你必须打开它。清单14-4 展示了 从代码中提供的URL创建一个CGPDFDocument对象的代码片段。注意此清单只是一个代码段,所以不是所有变量都定义了。此清单中用数字标注的代码行都有详细的解释。
清单 14-4 从一个URL打开一个PDF文档
CGPDFDocumentRef myDocument; |
myDocument = CGPDFDocumentCreateWithU |
if (myDocument == NULL) {// 2
|
error ("can't open `%s'.", filename); |
CFRelease (url); |
return EXIT_FAILURE; |
} |
CFRelease (url); |
if (CGPDFDocumentIsEncrypted |
if (!CGPDFDocumentUnlockWithP |
printf ("Enter password: "); |
fflush (stdout); |
password = fgets(buffer, sizeof(buffer), stdin); |
if (password != NULL) { |
buffer[strlen(buffer) - 1] = '\0'; |
if (!CGPDFDocumentUnlockWithP |
error("invalid password."); |
} |
} |
} |
if (!CGPDFDocumentIsUnlocked (myDocument)) {// 4
|
error("can't unlock `%s'.", filename); |
CGPDFDocumentRelease(myDocument); |
return EXIT_FAILURE; |
} |
} |
if (CGPDFDocumentGetNumberOf |
CGPDFDocumentRelease(document); |
return EXIT_FAILURE; |
} |
此处代码做了哪些:
1. 利用代码中提供的URL创建一个CGPDFDocument对象。
2. 检查以确保CGPDFDocument对象被创建。如果没有,代码退出,因为没有document继续执行没有意义。
3. 检查document是否加密。如果document加密,则代码试图利用空密码打开文档。如果失败,代码要求用户提供密码并试图用此密码解锁文档。
4. 检查document是否解锁,如果没有,代码退出。
5. 检查以确保document至少有一页,否则,代码退出。
扫描每页的内容流
清单14-5 代码段扫描文档每一页。当此scanner遇到一个注册了回调方法的PDF操作符,Quartz就调用此回调方法。此清单中用数字标注的代码行都有详细的解释。
清单 14-5 扫描文档的每一页
int k; |
CGPDFPageRef myPage; |
CGPDFScannerRef myScanner; |
CGPDFContentStreamRef myContentStream; |
|
numOfPages = CGPDFDocumentGetNumberOf |
for (k = 0; k < numOfPages; k++) { |
myPage = CGPDFDocumentGetPage (myDocument, k + 1 );// 2
|
myContentStream = CGPDFContentStreamCreate |
myScanner = CGPDFScannerCreate (myContentStream, myTable, NULL);// 4
|
CGPDFScannerScan (myScanner);// 5
|
CGPDFPageRelease (myPage);// 6
|
CGPDFScannerRelease (myScanner);// 7
|
CGPDFContentStreamReleas |
} |
CGPDFOperatorTableReleas |
此处代码解释:
1. 得到你先前打开的document的页数。见“Open thePDF Document.”
2. 检索扫描一页。页数以一开始。
3. 为此页创建内容流。
4. 为内容流创建一个scanner。你必须传入先前创建且在回调方法中设置的内容流和操作表。见“Create andSet Up the Operator Table.” 你也可以传入你的回调方法中需要的任何数据。
5. 解析与scanner相关联的内容流。当Quartz每次遇到你在回调方法中提供的操作符时就会调用相应回调方法。
6. 释放 page
7. 释放 scanner
8. 释放 content stream
9. 释放 operator table 在PDF扫描完所有页之后。