Core Graphics 之 PDF Document Creation, Viewing, and Transforming(十四)

PDF Document Creation, Viewing, and Transforming

PDF文档将独立于分辨率的矢量图形、文本和图像存储为一系列用紧凑编程语言编写的命令。PDF文档可以包含多页图像和文本。PDF对于创建跨平台的只读文档和绘制独立于分辨率的图形非常有用。

Quartz为所有应用程序创建了高保真的PDF文档,这些文档保留了应用程序的绘图操作,如图13-1所示。生成的PDF可以针对系统的其他部分或第三方产品的特定用途(例如特定打印机或web)进行优化。在预览和Acrobat中,由Quartz视图正确生成的PDF文档。


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

Quartz不仅使用PDF作为其“数字纸”,而且还将许多功能作为其API的一部分,您可以使用它们来显示和生成PDF文件,并完成许多其他与PDF相关的任务。

有关PDF的详细信息,包括PDF语言和语法,请参阅PDF参考资料,第四版,1.5版。

Opening and Viewing a PDF

Quartz提供数据类型CGPDFDocumentRef来表示PDF文档。您可以使用函数CGPDFDocumentCreateWithProvider或函数CGPDFDocumentCreateWithURL创建CGPDFDocument对象。创建CGPDFDocument对象后,可以将其绘制到图形上下文。图13-2显示了在窗口中显示的PDF文档。


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

清单13-1展示了如何创建CGPDFDocument对象并获取文档中的页数。清单后面显示了每一行代码的详细说明。
Listing 13-1 Creating a CGPDFDocument object from a PDF file

CGPDFDocumentRef MyGetPDFDocumentRef (const char *filename)
{
    CFStringRef path;
    CFURLRef url;
    CGPDFDocumentRef document;
    size_t count;
 
    path = CFStringCreateWithCString (NULL, filename,
                         kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 1
                        kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    document = CGPDFDocumentCreateWithURL (url);// 2
    CFRelease(url);
    count = CGPDFDocumentGetNumberOfPages (document);// 3
    if (count == 0) {
        printf("`%s' needs at least one page!", filename);
        return NULL;
    }
    return document;
}

下面是代码的作用:

调用Core Foundation函数,从CFString对象中创建一个CFURL对象,该对象表示要显示的PDF文件的文件名。
从CFURL对象创建CGPDFDocument对象。
获取PDF中的页数,以便代码中的下一个语句可以确保文档至少有一页。
通过查看清单13-2中的代码,您可以看到如何将PDF页面绘制到图形上下文。清单后面显示了每一行代码的详细说明。
Listing 13-2 Drawing a PDF page

void MyDisplayPDFPage (CGContextRef myContext,
                    size_t pageNumber,
                    const char *filename)
{
    CGPDFDocumentRef document;
    CGPDFPageRef page;
 
    document = MyGetPDFDocumentRef (filename);// 1
    page = CGPDFDocumentGetPage (document, pageNumber);// 2
    CGContextDrawPDFPage (myContext, page);// 3
    CGPDFDocumentRelease (document);// 4
}

下面是代码的作用:

调用您的函数(参见清单13-1),从您提供的文件名创建CGPDFDocument对象。
从PDF文档获取指定页码的页面。
通过调用函数CGContextDrawPDFPage,从PDF文件中绘制指定的页面。您需要提供图形上下文和要绘制的页面。
释放CGPDFDocument对象。

Creating a Transform for a PDF Page

Quartz提供了一个函数—— CGPDFPageGetDrawingTransform——它通过将PDF页面中的一个框映射到指定的矩形来创建仿射变换。该函数的原型是:

CGAffineTransform CGPDFPageGetDrawingTransform (
        CGPPageRef page,
        CGPDFBox box,
        CGRect rect,
        int rotate,
        bool preserveAspectRatio
);

该函数使用以下算法返回仿射变换:

交叉与您在box参数(媒体、裁剪、流血、修剪或艺术)和指定PDF页面的/MediaBox条目中指定的PDF框类型相关联的矩形。相交得到一个有效矩形。
根据PDF页面的/Rotate条目指定的数量旋转有效矩形。
将得到的矩形集中到rect参数中提供的矩形上。
如果您提供的旋转参数的值是非零,并且是90的倍数,那么该函数将根据您提供的角度旋转有效矩形。正的数值使矩形向右旋转;负值使矩形向左旋转。注意你通过的是度,而不是弧度。请记住,PDF页面的/Rotate条目也包含一个旋转,您提供的Rotate参数与/Rotate条目组合在一起。
如果需要,缩放有效矩形,使其与您提供的矩形的边重合。
如果您指定通过在preserveAspectRatio参数中传递true来保持长宽比,那么最终的矩形与在rect参数中提供的矩形的限制维数的边缘重合。
例如,如果您正在编写类似于图13-3所示的PDF查看应用程序,那么可以使用这个函数。如果要提供左/右旋转特性,可以调用CGPDFPageGetDrawingTransform来计算当前窗口大小和旋转设置的适当转换。


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

清单13-3显示了一个函数,它使用传递给函数的参数为PDF页面创建仿射转换,应用转换,然后绘制PDF页面。清单后面显示了每一行代码的详细说明。
Listing 13-3 Creating an affine transform for a PDF page

void MyDrawPDFPageInRect (CGContextRef context,
                    CGPDFPageRef page,
                    CGPDFBox box,
                    CGRect rect,
                    int rotation,
                    bool preserveAspectRatio)
{
    CGAffineTransform m;
 
    m = CGPDFPageGetDrawingTransform (page, box, rect, rotation,// 1
                                    preserveAspectRato);
    CGContextSaveGState (context);// 2
    CGContextConcatCTM (context, m);// 3
    CGContextClipToRect (context,CGPDFPageGetBoxRect (page, box));// 4
    CGContextDrawPDFPage (context, page);// 5
    CGContextRestoreGState (context);// 6
}

下面是代码的作用:

从提供给函数的参数创建仿射变换。
保存图形状态。
将CTM与仿射变换连接起来。
将图形上下文剪辑到框参数指定的矩形。函数CGPDFPageGetBoxRect获得与您提供的常量kcgpdfmediabox、kCGPDFCropBox、kCGPDFBleedBox、kCGPDFTrimBox或kcggpdfartbox相关联的页面边框(媒体、裁剪、流血、修剪和美术框)。
将PDF页面绘制到经过转换和剪切的上下文。
恢复图像的状态。

Creating a PDF File

使用Quartz 2D创建PDF文件就像绘制任何图形上下文一样简单。您可以为PDF文件指定一个位置,设置一个PDF图形上下文,并使用与任何图形上下文相同的绘图例程。清单13-4所示的函数MyCreatePDFFile显示了代码创建PDF文件时执行的所有任务。清单后面显示了每一行代码的详细说明。

注意,代码通过调用函数CGPDFContextBeginPage和CGPDFContextEndPage来描述PDF页面。您可以传递CFDictionary对象来指定页面属性,包括媒体、裁剪、流血、修剪和美术框。有关字典关键常量的列表和每个常量的更详细描述,请参阅CGPDFContext引用。
Listing 13-4 Creating a PDF file

void MyCreatePDFFile (CGRect pageRect, const char *filename)// 1
{
    CGContextRef pdfContext;
    CFStringRef path;
    CFURLRef url;
    CFDataRef boxData = NULL;
    CFMutableDictionaryRef myDictionary = NULL;
    CFMutableDictionaryRef pageDictionary = NULL;
 
    path = CFStringCreateWithCString (NULL, filename, // 2
                                kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 3
                     kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    myDictionary = CFDictionaryCreateMutable(NULL, 0,
                        &kCFTypeDictionaryKeyCallBacks,
                        &kCFTypeDictionaryValueCallBacks); // 4
    CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File"));
    CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name"));
    pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary); // 5
    CFRelease(myDictionary);
    CFRelease(url);
    pageDictionary = CFDictionaryCreateMutable(NULL, 0,
                        &kCFTypeDictionaryKeyCallBacks,
                        &kCFTypeDictionaryValueCallBacks); // 6
    boxData = CFDataCreate(NULL,(const UInt8 *)&pageRect, sizeof (CGRect));
    CFDictionarySetValue(pageDictionary, kCGPDFContextMediaBox, boxData);
    CGPDFContextBeginPage (pdfContext, pageDictionary); // 7
    myDrawContent (pdfContext);// 8
    CGPDFContextEndPage (pdfContext);// 9
    CGContextRelease (pdfContext);// 10
    CFRelease(pageDictionary); // 11
    CFRelease(boxData);
}

下面是代码的作用:

将指定PDF页面大小的矩形和指定文件名的字符串作为参数。
从传递给函数MyCreatePDFFile的文件名创建一个CFString对象。
从CFString对象创建一个CFURL对象。
创建一个空的CFDictionary对象来保存元数据。接下来的两行添加了标题和创建者。可以使用CFDictionarySetValue函数添加任意数量的键-值对。有关创建字典的更多信息,请参阅CFDictionary参考资料。
创建一个PDF图形上下文,传递三个参数:
指定PDF数据位置的CFURL对象。
一个指向矩形的指针,该矩形定义了PDF页面的默认大小和位置。矩形的原点通常是(0,0). Quartz使用这个矩形作为页面媒体框的默认边界。如果传递NULL, Quartz使用默认的页面大小为8.5 * 11英寸(612 * 792点)。
包含PDF元数据的CFDictionary对象。如果没有要添加的元数据,则传递NULL。
您可以使用CFDictionary对象来指定输出intent选项——intent子类型、条件、条件标识符、注册表名、目标输出概要文件和一个人类可读的文本字符串,该文本字符串包含关于预期目标设备或生产条件的附加信息或注释。有关输出意图选项的更多信息,请参阅CGPDFContext引用。
创建CFDictionary对象以保存PDF页面的页框。这个例子设置了媒体框。
标志着页的开始。当您使用支持多个页面(如PDF)的图形上下文时,您可以将CGPDFContextBeginPage函数与CGPDFContextEndPage一起调用,以在输出中划分页面边界。每个页面都必须加上对CGPDFContextBeginPage和CGPDFContextEndPage的调用。Quartz忽略了在基于页面的上下文中在页面边界之外执行的所有绘图操作。
调用应用程序定义的函数将内容绘制到PDF上下文。你在这里提供你的绘图程序。
表示基于页面的图形上下文中的页面结束。
释放PDF上下文。
释放页字典。

Adding Links

您可以向创建的PDF上下文添加链接和锚。Quartz提供三个函数,每个函数都以PDF图形上下文作为参数,以及链接信息:

CGPDFContextSetURLForRect允许在用户单击当前PDF页面中的矩形时指定要打开的URL。
CGPDFContextSetDestinationForRect允许您在用户单击当前PDF页面中的矩形时设置跳转到的目的地。您必须提供目标名称。
CGPDFContextAddDestinationAtPoint允许您在用户单击当前PDF页面中的某个点时设置跳转到的目的地。您必须提供目标名称。

Protecting PDF Content

为了保护PDF内容,您可以在传递给CGPDFContextCreate函数的辅助字典中指定许多安全选项。您可以设置所有者密码、用户密码,以及PDF是否可以打印或复制,在辅助字典中包括以下键:

kcgpdfcontext - townerpassword,用于定义PDF文档的所有者密码。如果指定了该密钥,则使用该值作为所有者密码对文档进行加密;否则,文档不会加密。这个键的值必须是一个可以用ASCII编码表示的CFString对象。只有前32个字节用于密码。此键没有默认值。如果这个键的值不能用ASCII表示,则不会创建文档,并且创建函数返回NULL。石英使用40位加密。
kCGPDFContextUserPassword,用于定义PDF文档的用户密码。如果对文档进行了加密,那么该密钥的值就是文档的用户密码。如果没有指定,则用户密码为空字符串。这个键的值必须是一个可以用ASCII编码表示的CFString对象;只有前32个字节用于密码。如果这个键的值不能用ASCII表示,则不会创建文档,并且创建函数返回NULL。
kCGPDFContextAllowsPrinting指定在使用用户密码解锁文档时是否可以打印该文档。这个键的值必须是一个CFBoolean对象。这个键的默认值是kCFBooleanTrue。
kCGPDFContextAllowsCopying指定文档在使用用户密码解锁时是否可以复制。这个键的值必须是一个CFBoolean对象。这个键的默认值是kCFBooleanTrue。
清单14-4(在下一章中)显示了检查PDF文档是否被锁定以及是否被锁定的代码,这些代码试图用密码打开文档。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容