【问题标题】:iOS SDK - Programmatically generate a PDF fileiOS SDK - 以编程方式生成 PDF 文件
【发布时间】:2011-05-20 17:59:11
【问题描述】:

在我看来,在以编程方式绘制 PDF 文件时,使用 CoreGraphics 框架是一项乏味的工作。

我想以编程方式创建一个 PDF,使用整个应用中视图中的各种对象。

我很想知道是否有任何适用于 iOS SDK 的好的 PDF 教程,也许是图书馆的一部分。

我看过这个教程,PDF Creation Tutorial,但它主要是用 C 编写的。寻找更多的 Objective-C 风格。这似乎也是一种写入 PDF 文件的荒谬方式,必须计算行和其他对象的放置位置。

void CreatePDFFile (CGRect pageRect, const char *filename) 
{   
    // This code block sets up our PDF Context so that we can draw to it
    CGContextRef pdfContext;
    CFStringRef path;
    CFURLRef url;
    CFMutableDictionaryRef myDictionary = NULL;

    // Create a CFString from the filename we provide to this method when we call it
    path = CFStringCreateWithCString (NULL, filename,
                                      kCFStringEncodingUTF8);

    // Create a CFURL using the CFString we just defined
    url = CFURLCreateWithFileSystemPath (NULL, path,
                                         kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    // This dictionary contains extra options mostly for 'signing' the PDF
    myDictionary = CFDictionaryCreateMutable(NULL, 0,
                                             &kCFTypeDictionaryKeyCallBacks,
                                             &kCFTypeDictionaryValueCallBacks);

    CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File"));
    CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name"));
    // Create our PDF Context with the CFURL, the CGRect we provide, and the above defined dictionary
    pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary);
    // Cleanup our mess
    CFRelease(myDictionary);
    CFRelease(url);
    // Done creating our PDF Context, now it's time to draw to it

    // Starts our first page
    CGContextBeginPage (pdfContext, &pageRect);

    // Draws a black rectangle around the page inset by 50 on all sides
    CGContextStrokeRect(pdfContext, CGRectMake(50, 50, pageRect.size.width - 100, pageRect.size.height - 100));

    // This code block will create an image that we then draw to the page
    const char *picture = "Picture";
    CGImageRef image;
    CGDataProviderRef provider;
    CFStringRef picturePath;
    CFURLRef pictureURL;

    picturePath = CFStringCreateWithCString (NULL, picture,
                                             kCFStringEncodingUTF8);
    pictureURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), picturePath, CFSTR("png"), NULL);
    CFRelease(picturePath);
    provider = CGDataProviderCreateWithURL (pictureURL);
    CFRelease (pictureURL);
    image = CGImageCreateWithPNGDataProvider (provider, NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease (provider);
    CGContextDrawImage (pdfContext, CGRectMake(200, 200, 207, 385),image);
    CGImageRelease (image);
    // End image code

    // Adding some text on top of the image we just added
    CGContextSelectFont (pdfContext, "Helvetica", 16, kCGEncodingMacRoman);
    CGContextSetTextDrawingMode (pdfContext, kCGTextFill);
    CGContextSetRGBFillColor (pdfContext, 0, 0, 0, 1);
    const char *text = "Hello World!";
    CGContextShowTextAtPoint (pdfContext, 260, 390, text, strlen(text));
    // End text

    // We are done drawing to this page, let's end it
    // We could add as many pages as we wanted using CGContextBeginPage/CGContextEndPage
    CGContextEndPage (pdfContext);

    // We are done with our context now, so we release it
    CGContextRelease (pdfContext);
}

编辑:这是一个 iPhone 项目中GitHub using libHaru 的示例。

【问题讨论】:

  • 哈哈,我知道这很旧,但 iPhone 上的 100 页 pdf 不算什么。它会给服务器带来比 iOS 设备更大的压力,因为服务器必须将其乘以尝试执行此操作的并发用户数。

标签: iphone objective-c ipad ios pdf-generation


【解决方案1】:

这是一个迟到的回复,但由于我在生成 pdf 方面遇到了很多困难,我认为值得分享我的观点。除了 Core graphics,创建上下文,您还可以使用 UIKit 方法生成 pdf。

Apple 在drawing and printing guide. 中很好地记录了它

【讨论】:

  • FWIW——本教程的网站在 Godaddy 中是死路一条。
【解决方案2】:

几件事...

首先,iOS 中的 CoreGraphics PDF 生成存在一个错误,导致 PDF 损坏。我知道这个问题一直存在到并包括 iOS 4.1(我还没有测试过 iOS 4.2)。该问题与字体有关,仅当您在 PDF 中包含文本时才会出现。症状是,在生成 PDF 时,您会在调试控制台中看到如下所示的错误:

<Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT'

棘手的方面是生成的 PDF 将在某些 PDF 阅读器中呈现良好,但无法在其他地方呈现。因此,如果您可以控制用于打开 PDF 的软件,则可以忽略此问题(例如,如果您只打算在 iPhone 或 Mac 桌面上显示 PDF,那么您应该可以使用核心图形)。但是,如果您需要创建在任何地方都可以使用的 PDF,那么您应该仔细研究一下这个问题。以下是一些附加信息:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/15505-pdf-font-problem-cant-get-cids-glyphs.html#post97854

作为一种解决方法,我已在 iPhone 上成功使用 libHaru 作为 CoreGraphics PDF 生成的替代品。最初让 libHaru 与我的项目一起构建有点棘手,但是一旦我正确设置了项目,它就可以很好地满足我的需求。

其次,根据 PDF 的格式/布局,您可以考虑使用 Interface Builder 创建一个视图,作为 PDF 输出的“模板”。然后,您将编写代码来加载视图、填写任何数据(例如,为 UILabel 设置文本等),然后将视图的各个元素呈现到 PDF 中。换句话说,使用 IB 来指定坐标、字体、图像等,并编写代码以通用方式呈现各种元素(例如,UILabelUIImageView 等),这样您就不必费力了编码一切。我使用了这种方法,它非常适合我的需求。同样,这对您的情况可能有意义,也可能没有意义,具体取决于 PDF 的格式/布局需求。

编辑:(回复第一条评论)

我的实现是商业产品的一部分,这意味着我不能分享完整的代码,但我可以给出一个大致的大纲:

我创建了一个带有视图的 .xib 文件,并将视图的大小调整为 850 x 1100(我的 PDF 的目标是 8.5 x 11 英寸,因此可以轻松地与设计时坐标进行转换)。

在代码中,我加载视图:

- (UIView *)loadTemplate
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ReportTemplate" owner:self options:nil];
    for (id view in nib) {
        if ([view isKindOfClass: [UIView class]]) {
            return view;
        }
    }

    return nil;
}

然后我填写各种元素。我使用标签来查找适当的元素,但您可以通过其他方式进行此操作。示例:

UILabel *label = (UILabel *)[templateView viewWithTag:TAG_FIRST_NAME];
if (label != nil) {
    label.text = (firstName != nil) ? firstName : @"None";

然后我调用一个函数来将视图呈现为 PDF 文件。此函数递归遍历视图层次结构并渲染每个子视图。对于我的项目,我只需要支持 Label、ImageView 和 View(用于嵌套视图):

- (void)addObject:(UIView *)view
{
    if (view != nil && !view.hidden) {
        if ([view isKindOfClass:[UILabel class]]) {
            [self addLabel:(UILabel *)view];
        } else if ([view isKindOfClass:[UIImageView class]]) {
            [self addImageView:(UIImageView *)view];
        } else if ([view isKindOfClass:[UIView class]]) {
            [self addContainer:view];
        }
    }
}

作为一个例子,这是我对 addImageView 的实现(HPDF_ 函数来自 libHaru):

- (void)addImageView:(UIImageView *)imageView
{
    NSData *pngData = UIImagePNGRepresentation(imageView.image);
    if (pngData != nil) {
        HPDF_Image image = HPDF_LoadPngImageFromMem(_pdf, [pngData bytes], [pngData length]);
        if (image != NULL) {
            CGRect destRect = [self rectToPDF:imageView.frame];

            float x = destRect.origin.x;
            float y = destRect.origin.y - destRect.size.height;
            float width = destRect.size.width;
            float height = destRect.size.height;

            HPDF_Page page = HPDF_GetCurrentPage(_pdf);
            HPDF_Page_DrawImage(page, image, x, y, width, height);
        }
    }
}

希望这能给你这个想法。

【讨论】:

  • 您有任何如何使用 XIB 作为模板的示例吗?
【解决方案3】:

迟到,但可能对其他人有帮助。听起来 PDF 模板样式的操作可能是您想要的方法,而不是在代码中构建 PDF。由于您无论如何都想通过电子邮件发送它,因此您已连接到网络,因此您可以使用Docmosis 云服务之类的东西,它实际上是一种邮件合并服务。将数据/图像发送给它以与您的模板合并。这种方法的好处是代码少得多,并且可以从 iPad 应用程序中卸载大部分处理。我已经看到它在 iPad 应用程序中使用过,非常棒。

【讨论】:

    【解决方案4】:

    iOS 上的 PDF 函数都是基于 CoreGraphics 的,这是有道理的,因为 iOS 中的绘制图元也是基于 CoreGraphics。如果您想将 2D 图元直接渲染到 PDF 中,则必须使用 CoreGraphics。

    UIKit 中也有一些对象的快捷方式,比如图像。将 PNG 绘制到 PDF 上下文仍然需要调用 CGContextDrawImage,但您可以使用从 UIImage 获取的 CGImage 来完成,例如:

    UIImage * myPNG = [UIImage imageNamed:@"mypng.png"];
    CGContextDrawImage (pdfContext, CGRectMake(200, 200, 207, 385), [myPNG CGImage]);
    

    如果您需要有关整体设计的更多指导,请更明确地说明“整个应用程序中的各种对象”的含义以及您要完成的工作。

    【讨论】:

    • 感谢您的回复。通过对象,我的意思是我正在获取用户输入的文本并希望将它们放置在 PDF 上的不同位置。正如你所指出的,一些图形。类似于可以通过电子邮件发送的表格。
    • 您好,我有一个问题:通过这种方式(CGContextDrawImage),pdf 不再是矢量了。如果我想将多个矢量图形渲染到一个pdf的单页中怎么办,同时确保pdf也是矢量
    猜你喜欢
    • 2016-07-29
    • 2018-01-29
    • 2014-08-10
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 2013-10-18
    • 1970-01-01
    相关资源
    最近更新 更多