iOS SDK - програмне створення PDF-файлу


79

Використання фреймворка CoreGraphics - це, на мою чесну думку, нудна робота, коли мова йде про програмне малювання файлу PDF.

Я хотів би програмно створити PDF , використовуючи різні об’єкти з подань у моєму додатку.

Мені цікаво знати, чи є якісь хороші навчальні посібники у форматі PDF для iOS SDK, можливо, падіння в бібліотеці.

Я бачив цей підручник, Підручник зі створення PDF , але він здебільшого був написаний на 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);
}

EDIT: Ось приклад на GitHub з використанням libHaru у проекті iPhone.


1
ха-ха, я знаю, що це старе, але PDF на 100 сторінок на iPhone - це нічого. це наголосить на сервері більше, ніж на пристрої iOS, оскільки серверу доведеться помножити це на кількість одночасних користувачів, які намагаються це зробити.
Арманд

Відповіді:


56

Кілька речей ...

По-перше, є помилка генерації PDF-файлів CoreGraphics в iOS, яка призводить до пошкодження PDF-файлів. Я знаю, що ця проблема існує до iOS 4.1 включно (я не тестував iOS 4.2). Проблема пов’язана зі шрифтами і з’являється лише в тому випадку, якщо ви включили текст у свій PDF. Симптом полягає в тому, що під час створення PDF-файлу ви побачите помилки на консолі налагодження, які виглядають так:

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

Хитрий аспект полягає в тому, що отриманий PDF-файл буде чудово відображатися в деяких програмах зчитування PDF-файлів, але не відображається в інших місцях. Отже, якщо у вас є контроль над програмним забезпеченням, яке буде використовуватися для відкриття PDF-файлу, ви можете проігнорувати цю проблему (наприклад, якщо ви збираєтеся відображати PDF-файли лише на робочих столах iPhone або Mac, тоді вам слід добре працювати CoreGraphics). Однак, якщо вам потрібно створити PDF-файл, який працює де завгодно, вам слід уважніше розглянути це питання. Ось додаткова інформація:

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

Як обхідний шлях, я успішно використав libHaru на iPhone як заміну генерації CoreGraphics PDF. Спочатку було складно отримати libHaru для створення з моїм проектом, але як тільки я правильно налаштував свій проект, він прекрасно працював для моїх потреб.

По-друге, залежно від формату / макета вашого PDF, ви можете розглянути можливість використання Interface Builder для створення подання, яке служить "шаблоном" для виводу PDF. Потім ви пишете код для завантаження подання, заповнюєте будь-які дані (наприклад, встановлюєте текст для UILabels тощо), а потім відтворюєте окремі елементи подання в PDF. Іншими словами, використання IB , щоб вказати координати, шрифти, зображення і т.д. , і писати код для надання різних елементів (наприклад, UILabel, UIImageViewі т.д.) , в загальному вигляді , так що вам не доведеться прописувати все. Я застосував такий підхід, і він спрацював чудово для моїх потреб. Знову ж таки, це може мати або не мати сенсу для вашої ситуації, залежно від потреб форматування / макетування вашого PDF.

EDIT: (відповідь на 1-й коментар)

Моя реалізація є частиною комерційного продукту, що означає, що я не можу поділитися повним кодом, але можу дати загальну схему:

Я створив файл .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 як шаблон?
WrightsCS

12

Це пізня відповідь, але оскільки я багато боровся з генерацією PDF, то вважав, що варто поділитися своїми думками. Замість Core graphics для створення контексту ви також можете використовувати методи UIKit для створення PDF-файлу.

Apple добре задокументувала це в керівництві з малювання та друку.


4
FWIW - сайт для підручника безвихідний для Godaddy.
CuriousRabbit

8

Функції PDF у iOS засновані на CoreGraphics, що має сенс, оскільки примітиви малювання в iOS також засновані на CoreGraphics. Якщо ви хочете рендерити 2D примітиви безпосередньо в PDF, вам доведеться використовувати CoreGraphics.

Є кілька ярликів для об’єктів, які також мешкають в UIKit, як-от зображення. Малювання PNG у контексті PDF все ще вимагає виклику CGContextDrawImage, але ви можете зробити це за допомогою CGImage, який ви можете отримати з UIImage, наприклад:

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

Якщо ви хочете отримати більше вказівок щодо загального дизайну, будь ласка, чіткіше розкажіть, що ви маєте на увазі під словом «різні об’єкти у вашому додатку» та що ви намагаєтесь досягти.


Дякую за відповідь. Під об’єктами я просто маю на увазі, що беру введений користувачем текст і хочу розмістити їх у різних місцях PDF. І як ви вже зазначали, деякі графічні зображення. SO щось на зразок форми, яку можна надіслати електронною поштою.
WrightsCS

Привіт, у мене є запитання: таким чином (CGContextDrawImage) pdf більше не є векторним. Що мені робити, якщо я хочу зробити декілька векторних графічних зображень на одній сторінці PDF, тим часом переконайтесь, що PDF також векторний
Paradise

2

Пізно, але, можливо, корисний іншим. Схоже, стиль роботи з шаблоном PDF може бути підходом, який ви хочете, а не будувати PDF у коді. Оскільки ви все одно хочете надіслати його електронною поштою, ви підключені до мережі, щоб використовувати щось на зразок хмарної служби Docmosis, яка фактично є службою злиття пошти. Надішліть йому дані / зображення для злиття з вашим шаблоном. Такий підхід має набагато менше коду та вивантажує більшу частину обробки з вашого додатка iPad. Я бачив, як він використовується в додатку для iPad, і це було приємно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.