Core Graphics 之 PDF Document Parsing (十五)

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来获得页面字典。


屏幕快照 2018-08-28 下午1.17.03.png

您可以通过访问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);
屏幕快照 2018-08-28 下午1.17.56.png

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操作符。


屏幕快照 2018-08-28 下午1.20.17.png
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中的所有页面后释放操作员表。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 83,269评论 1 181
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 29,905评论 1 151
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 34,972评论 0 105
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 19,267评论 0 90
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 24,625评论 0 152
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 20,407评论 1 91
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 13,069评论 2 168
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 12,451评论 0 84
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 10,989评论 5 117
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 14,269评论 0 132
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 12,928评论 1 131
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 13,773评论 0 135
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 8,550评论 0 18
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 11,390评论 2 122
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 14,580评论 3 132
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 10,137评论 0 3
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 10,499评论 0 83
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 15,175评论 2 142
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 15,641评论 2 137

推荐阅读更多精彩内容