iOS绘图和打印编程指导(五)-生成PDF内容

UIKit提供大量函数支持使用原生代码生成PDF内容. 这些函数允许你创建一个以PDF文件或PDF数据为目标的绘图上下文. 然后你可以创建一个或多个PDF页面, 使用的UIKit和Core Graphic技术, 和绘制到屏幕一样的步骤将PDF绘制到这些页面. 当你完成后, 剩下的就是PDF文件.

整个绘制PDF过程看起来很像绘制image(本系列文章四已讲), 包括一些步骤:

  1. 创建PDF上下文, 然后将它push到Graphic堆栈
  2. 创建页面(page)
  3. 使用UIKit或者Core Graphic常规方法绘制内容到页面
  4. 如果需要, 可以添加链接
  5. 重复2,3,4这三个步骤, 如果需要的话
  6. 最后将PDF context从堆栈中pop出来, 然后将结果写入到具体的PDF文件或者保存到制定的NSMutableData对象, 这取决于context是如何创建的.

下面的章节就是围绕上面的步骤具体展开的, 包括使用代码展示如何实现上的步骤.

创建和配置PDF上下文


创建PDF上下文的函数有两个, 分别是UIGraphicsBeginPDFContextToDataUIGraphicsBeginPDFContextToFile. 这两个函数将不同PDF数据输出地和图形上下文绑定, UIGraphicsBeginPDFContextToData函数式将数据输出到NSMutableData对象, 而UIGraphicsBeginPDFContextToFile函数是将内容输出到PDF文件(APP的home路径下)

PDF文件是以页结构来管理内容的. 所以在绘制时有两个限制如下:

  • 在将内容绘制到page之前, 你需要open该page
  • 你必须设置page的大小.

在创建PDF绘图上下文时, 那两个函数会默认指定page的大小, 但不会自动打开page. 创建上下文后, 必须使用UIGraphicsBeginPDFPageUIGraphicsBeginPDFPageWithInfo函数显式打开新page. 每次创建新page时, 必须再次调用这些函数中的一个来标记新page的开始. UIGraphicsBeginPDFPage函数使用默认大小创建一个page, UIGraphicsBeginPDFPageWithInfo函数允许你自定义page大小和其他属性.

完成绘制后, 通过调用UIGraphicsBeginPDFPage函数来关闭PDF上下文. 同时还会关闭最后一页, 并将PDF内容写入到创建上下文时指定的文件或者数据对象中. 此函数还从上下文堆栈中移除PDF上下文.

代码5-1展示了应用程序在textView中获取文本然后循环写入PDF文件中. 除了配置和管理PDF上下文的函数调用之外, 大多数代码都与绘制所需的内容有关. textView是一个成员变量保存包含所文本的text view对象. 该应用程序使用Core Text框架(更具体的说是CTFramesetterRef数据类型)来处理连续页面上的文本布局和页面管理. 自定义方法renderPageWithTextRange:andFramesetter:drawPageNumber:方的实现如代码5-2所示

代码清单4-1 创建一个新的PDF文件

- (IBAction)savePDFFile:(id)sender {
    // Prepare the text using a Core Text Framesetter.
    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, (CFStringRef)textView.text, NULL);
    if (currentText) {
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
        if (framesetter) {
 
            NSString *pdfFileName = [self getPDFFileName];
            // Create the PDF context using the default page size of 612 x 792.
            UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
 
            CFRange currentRange = CFRangeMake(0, 0);
            NSInteger currentPage = 0;
            BOOL done = NO;
 
            do {
                // Mark the beginning of a new page.
                UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
 
                // Draw a page number at the bottom of each page.
                currentPage++;
                [self drawPageNumber:currentPage];
 
                // Render the current page and update the current range to
                // point to the beginning of the next page.
                currentRange = [self renderPageWithTextRange:currentRange andFramesetter:framesetter];
 
                // If we're at the end of the text, exit the loop.
                if (currentRange.location == CFAttributedStringGetLength((CFAttributedStringRef)currentText))
                    done = YES;
            } while (!done);
 
            // Close the PDF context and write the contents out.
            UIGraphicsEndPDFContext();
 
            // Release the framewetter.
            CFRelease(framesetter);
 
        } else {
            NSLog(@"Could not create the framesetter needed to lay out the atrributed string.");
        }
        // Release the attributed string.
        CFRelease(currentText);
    } else {
            NSLog(@"Could not create the attributed string for the framesetter");
    }
}

绘制PDF页面


所有的PDF内容绘制需要在page中完成. 每个PDF文档至少有一个page, 但是多数是多个page. 你通过调用UIGraphicsBeginPDFPageUIGraphicsBeginPDFPageWithInfo函数来指定新的page. 这些函数关闭前一个page(如果它打开的话), 然后创建一个新的page, 并为绘图做好准备.

创建page之后, PDF绘图上下文将捕获所有绘图命令, 并将其转换为PDF命令. 你可以在页面中绘制任何你想要的东西, 包括文本, 矢量形状和图像, 就像在APP中的自定义view中一样. 你发出的绘图命令会被PDF上下文捕获并翻译成PDF数据. 在页面上放置内容完全取决于你, 但必须在page中size范围内进行.

代码5-2展示了在PDF页中绘制内容的两种自定义方法. renderPageWithTextRange:andFramesetter:方法使用Core Text创建一个适合页面的文本框架, 然后在该框架中布局一些文本. 在布局文本之后, 它返回一个更新的range, 该range反映了当前页面的结束和下一页的开始. drawPageNumber:方法使用NSString的绘制功能在PDF的每页底部绘制一个页码字符串.

注意:此代码片段使用了Core Text框架, 你需要手动添加该框架到你的项目中

代码清单5-2 绘制PDF页面内容

// Use Core Text to draw the text in a frame on the page.
- (CFRange)renderPage:(NSInteger)pageNum withTextRange:(CFRange)currentRange
                 andFramesetter:(CTFramesetterRef)framesetter {
   // Get the graphics context.
   CGContextRef    currentContext = UIGraphicsGetCurrentContext();
 
   // Put the text matrix into a known state. This ensures
   // that no old scaling factors are left in place.
   CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
 
   // Create a path object to enclose the text. Use 72 point
   // margins all around the text.
   CGRect    frameRect = CGRectMake(72, 72, 468, 648);
   CGMutablePathRef framePath = CGPathCreateMutable();
   CGPathAddRect(framePath, NULL, frameRect);
 
   // Get the frame that will do the rendering.
   // The currentRange variable specifies only the starting point. The framesetter
   // lays out as much text as will fit into the frame.
   CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
   CGPathRelease(framePath);
 
   // Core Text draws from the bottom-left corner up, so flip
   // the current transform prior to drawing.
   CGContextTranslateCTM(currentContext, 0, 792);
   CGContextScaleCTM(currentContext, 1.0, -1.0);
 
   // Draw the frame.
   CTFrameDraw(frameRef, currentContext);
 
   // Update the current range based on what was drawn.
   currentRange = CTFrameGetVisibleStringRange(frameRef);
   currentRange.location += currentRange.length;
   currentRange.length = 0;
   CFRelease(frameRef);
 
   return currentRange;
}
 
- (void)drawPageNumber:(NSInteger)pageNum {
   NSString *pageString = [NSString stringWithFormat:@"Page %d", pageNum];
   UIFont *theFont = [UIFont systemFontOfSize:12];
   CGSize maxSize = CGSizeMake(612, 72);
 
   CGSize pageStringSize = [pageString sizeWithFont:theFont
                                       constrainedToSize:maxSize
                                       lineBreakMode:UILineBreakModeClip];
   CGRect stringRect = CGRectMake(((612.0 - pageStringSize.width) / 2.0),
                                  720.0 + ((72.0 - pageStringSize.height) / 2.0),
                                  pageStringSize.width,
                                  pageStringSize.height);
 
   [pageString drawInRect:stringRect withFont:theFont];
}

创建PDF内容链接


除了绘制PDF内容外, 可以在内容中加入链接(将用户带到同一PDF文档中其他位置或者外部URL链接). 创建单个链接, 必须向PDF文档页面添加链接的源矩形区域和链接的目的地. 链接目的地的属性之一是作为该链接的唯一表示符(字符串). 要创建到特定目的地的链接, 需要在创建源矩形区域时, 指定链接目的地的标识符.

若要向PDF内容添加新的链接目的地, 请使用UIGraphicsAddPDFContextDestinationAtPoint函数. 这个函数将一个命名(唯一标识符)好的目的地和当前page中的具体点关联起来. 当希望链接到该目的地时, 可以使用UIGraphicsSetPDFContextDestinationForRect函数将链接目的地和链接原矩形区域关联起来. 图5-1显示了上面两个函数的关系. 当用户点击"see Chapter 1"文本时, PDF会跳到目的地-"第一章", 该目的点位于第一章的顶部

图5-1 创建一个链接目的地并跳转到点

另外, 如果要在PDF中创建指向外部URL的链接时, 可以使用UIGraphicsSetPDFContextURLForRect函数来设置.

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

推荐阅读更多精彩内容