PDF Document Parsing
Quartz提供了一些功能,允许您检查PDF文档结构和内容流。检查文档结构可以让您阅读文档目录中的条目以及与每个条目相关联的内容。通过递归地遍历目录,您可以检查整个文档。
一个PDF内容流正是它的名字所暗示的——一个连续的数据流,比如‘BT 12 /F71 Tf(绘制此文本)Tj…其中,PDF操作符及其描述符与实际的PDF内容混合在一起。检查内容流要求您按顺序访问它。
本章展示了如何检查PDF文档的结构并解析PDF文档的内容。
Inspecting PDF Document Structure
PDF文件可能包含多页图像和文本。您可以使用Quartz访问文档和页面级别的元数据以及PDF页面上的对象。本节简要介绍了可以访问的元数据。
PDF文档对象(CGPDFDocument)包含与PDF文档相关的所有信息,包括其目录和内容。目录中的条目递归地描述了PDF文档的内容。您可以通过调用函数CGPDFDocumentGetCatalog来访问PDF文档目录的内容。
PDF页面对象(CGPDFPage)表示PDF文档中的一个页面,包含与特定页面相关的信息,包括页面字典和页面内容。您可以通过调用函数CGPDFPageGetDictionary来获得页面字典。
您可以通过访问PDF元数据获得更多有用的信息。图14-1中的项目只是一个示例。例如,您可以使用清单14-1所示的代码检查PDF是否具有缩略图图像(如图14-2所示)。
Listing 14-1 Getting a thumbnail view of a PDF
CGPDFDictionaryRef d;
CGPDFStreamRef stream; // represents a sequence of bytes
d = CGPDFPageGetDictionary(page);
// check for thumbnail data
if (CGPDFDictionaryGetStream (d, “Thumb”, &stream)){
// get the data if it exists
data = CGPDFStreamCopyData (stream, &format);
Quartz提供了许多函数,您可以使用这些函数来获取PDF元数据项的单独值。使用CGPDFObjectGetValue函数,传递一个CGPDFObjectRef、一个PDF对象类型(kCGPDFObjectTypeBoolean、kCGPDFObjectTypeInteger,等等)和值的存储。返回时,存储空间中充满了值。
您还可以使用许多其他函数来遍历PDF文件的层次结构,以访问各种节点及其子节点。例如,CGPDFArray函数(CGPDFArrayGetBoolean、CGPDFArrayGetDictionary、CGPDFArrayGetInteger等等)允许您访问值数组以检索特定类型的值。通过阅读PDF规范,您可以了解关于如何使用这些函数的更多信息。
Parsing PDF Content
PDF内容流包含表示应用程序可能感兴趣的PDF内容流的部分操作符。运算符要么标记一个点,要么标记一个序列。操作符被指定为具有属性列表或与其关联的对象的标记。标记指定点或内容序列表示什么。属性列表是包含PDF内容创建者指定的键-值对的字典。在解析PDF内容流时,应用程序查找感兴趣的标记,检查与标记相关联的标记、属性列表或对象,然后执行任何适当的进一步处理。有关PDF操作符的完整列表,请参阅PDF参考资料。
使用CGPDFScanner对象(CGPDFScannerRef数据类型)来解析PDF内容流。CGPDFScanner对象为流中的任何操作符调用回调,您已经为其注册了一个回调。
您可以执行以下部分中描述的任务来解析内容流:
为运营商编写回调。您只需要为您想要处理的操作符编写回调。
创建和设置操作符表。
打开PDF文档。
扫描每个页面的内容流。
在适当的情况下,您需要确保释放了扫描程序、内容流和操作符表。
下面几节将展示如何解析内容流以查找标记内容操作符(参见表14-1)。标记的内容操作符仅表示PDF内容中使用的一些PDF操作符。当您编写自己的代码时,您将寻找适合您的应用程序的PDF操作符。
Write Callbacks for Operators
当Quartz为PDF操作符调用回调时,它会传递一个CGPDFScanner对象和一个指向回调所需信息的指针。通常,回调会检索与运算符关联的任何项。例如,清单14-2所示的' MP '操作符的回调调用函数' CGPDFScannerPopName '来从堆栈中检索与操作符关联的字符串。如果清单中的代码成功地从扫描堆栈中检索到名称,那么它将打印名称。
Quartz具有各种各样的“CGPDFScannerPop”函数,用于检索对象、布尔值、名称、数字、字符串、数组、字典和流。每个函数返回一个布尔值,以指示是否成功检索项。
Listing 14-2 A callback for the MP operator
static void
op_MP (CGPDFScannerRef s, void *info)
{
const char *name;
if (!CGPDFScannerPopName(s, &name))
return;
printf("MP /%s\n", name);
}
Create and Set Up the Operator Table
CGPDFOperatorTable对象存储您编写的PDF操作符回调函数。函数CGPDFOperatorTableCreate创建一个操作符表,如清单14-3所示。在创建操作符表之后,为要添加到表中的每个回调调用cgpdfoperator汤匙etcallback函数。传递这个表、指定PDF操作符的字符串以及一个指向回调函数的指针。您可以任意命名回调。只是要确保传递给函数cgpdfoperator汤匙etcallback的回调名没有拼错。
清单14-3中的代码为表14-1中列出的每个标记内容操作符设置了回调。您的应用程序将只为感兴趣的操作符设置回调。PDF操作符字符串在Adobe的PDF引用中定义。
Listing 14-3 Setting callbacks for an operator table
CGPDFOperatorTableRef myTable;
myTable = CGPDFOperatorTableCreate();
CGPDFOperatorTableSetCallback (myTable, "MP", &op_MP);
CGPDFOperatorTableSetCallback (myTable, "DP", &op_DP);
CGPDFOperatorTableSetCallback (myTable, "BMC", &op_BMC);
CGPDFOperatorTableSetCallback (myTable, "BDC", &op_BDC);
CGPDFOperatorTableSetCallback (myTable, "EMC", &op_EMC);
Open the PDF Document
在扫描PDF文档的内容之前,需要先打开它。清单14-4显示了一个代码片段,它从提供给代码的URL创建了一个CGPDFDocument对象。注意,清单是一个代码片段,因此并不是所有变量都被声明。清单后面显示了每一行代码的详细说明。
Listing 14-4 Opening a PDF document from a URL
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL(url);// 1
if (myDocument == NULL) {// 2
error ("can't open `%s'.", filename);
CFRelease (url);
return EXIT_FAILURE;
}
CFRelease (url);
if (CGPDFDocumentIsEncrypted (myDocument)) {// 3
if (!CGPDFDocumentUnlockWithPassword (myDocument, "")) {
printf ("Enter password: ");
fflush (stdout);
password = fgets(buffer, sizeof(buffer), stdin);
if (password != NULL) {
buffer[strlen(buffer) - 1] = '\0';
if (!CGPDFDocumentUnlockWithPassword (myDocument, password))
error("invalid password.");
}
}
}
if (!CGPDFDocumentIsUnlocked (myDocument)) {// 4
error("can't unlock `%s'.", filename);
CGPDFDocumentRelease(myDocument);
return EXIT_FAILURE;
}
}
if (CGPDFDocumentGetNumberOfPages(myDocument) == 0) {// 5
CGPDFDocumentRelease(myDocument);
return EXIT_FAILURE;
}
下面是代码的作用:
从提供给代码的URL创建CGPDFDocument对象。
检查以确保创建了CGPDFDocument对象。如果没有,则代码退出,因为没有文档继续下去是没有意义的。
检查文档是否加密。如果对文档进行了加密,则试图打开的代码将使用空白密码。如果失败,代码将询问用户输入密码并尝试用密码解锁文档。
检查文档是否解锁。如果不是,则代码退出。
检查以确保文档至少有一页。否则,退出的代码。
Scan the Content Stream for Each Page
清单14-5中的代码片段扫描文档中的每个页面。当扫描器遇到注册回调的PDF操作符之一时,Quartz将调用回调。下面是对每一行代码的详细说明。
Listing 14-5 Scanning each page of a document
int k;
CGPDFPageRef myPage;
CGPDFScannerRef myScanner;
CGPDFContentStreamRef myContentStream;
numOfPages = CGPDFDocumentGetNumberOfPages (myDocument);// 1
for (k = 0; k < numOfPages; k++) {
myPage = CGPDFDocumentGetPage (myDocument, k + 1 );// 2
myContentStream = CGPDFContentStreamCreateWithPage (myPage);// 3
myScanner = CGPDFScannerCreate (myContentStream, myTable, NULL);// 4
CGPDFScannerScan (myScanner);// 5
CGPDFPageRelease (myPage);// 6
CGPDFScannerRelease (myScanner);// 7
CGPDFContentStreamRelease (myContentStream);// 8
}
CGPDFOperatorTableRelease(myTable);
下面是代码的作用:
获取先前打开的文档中的页数。请参阅打开PDF文档。
检索要扫描的页面。页码从1开始。
为页面创建内容流。
为内容流创建一个扫描器。您必须传递内容流和之前使用回调创建和设置的操作符表。请参阅创建和设置操作符表。您还可以传递回调所需的任何数据。
解析与扫描器关联的内容流。每当Quartz遇到为其提供回调的操作符之一时,它就会调用回调函数。
发布页面。
释放扫描仪。
释放内容流。
扫描PDF中的所有页面后释放操作员表。