Реализация генерации PDF в мобильном приложении
Клиенты часто просят выгрузить счёт или акт из приложения. Мы столкнулись с этой задачей в проекте для финансового сервиса: нужно было за 2 недели сделать генерацию invoice.pdf с логотипом, таблицей позиций и кириллицей. Подход выбрали разный для iOS и Android, но результат один. Мы занимаемся мобильной разработкой более 5 лет и реализовали генерацию PDF для 30+ приложений. Сейчас расскажем, как решаем такие задачи под ключ.
Каждый проект уникален по сложности вёрстки и требованиям к шрифтам, но общие принципы остаются неизменными. Выбор подхода упирается в сложность документа — простой текст плюс таблица решается Canvas API, а сложная вёрстка с изображениями и нестандартными шрифтами требует HTML → PDF. Сравним в таблице.
| Подход | iOS | Android | Сложность вёрстки | Рекомендация |
|---|---|---|---|---|
| Canvas API | UIGraphicsPDFRenderer | PdfDocument | Низкая-средняя | Простые счета, акты |
| HTML → PDF | WKWebView.createPDF | WebView + PrintManager | Высокая | Сложные отчёты, брошюры |
| Библиотеки (iText, OpenPDF) | – | iText 7 / OpenPDF | Любая | Коммерческие шаблоны |
Для документов с нестандартными шрифтами или сложной сеткой HTML-подход даёт больше гибкости, но требует осторожности с разрывами страниц. Canvas API, напротив, даёт полный контроль над пикселями, но каждый элемент приходится позиционировать вручную. Получите консультацию по вашему проекту — мы поможем подобрать оптимальный подход.
Как выбрать подход к генерации PDF?
Выбор упирается в сложность документа. Если нужен простой счёт с парой полей и таблицей — используйте Canvas API. Для многостраничных отчётов с логотипами, колонтитулами и произвольными шрифтами — HTML→PDF. У нас был случай, когда клиент хотел PDF с динамическими графиками; мы выбрали HTML+svg и конвертировали через WKWebView. Всё влезло на 3 страницы с автоматическими разрывами.
iOS: PDFKit и UIGraphicsPDFRenderer
На iOS два основных подхода.
UIGraphicsPDFRenderer — рисуем PDF как Canvas. Полный контроль над расположением, но каждый элемент надо размещать вручную. Вот пример генерации счёта:
func generateInvoicePDF(invoice: Invoice) -> Data { let pageRect = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4 в pt let renderer = UIGraphicsPDFRenderer(bounds: pageRect) return renderer.pdfData { context in context.beginPage() let ctx = context.cgContext // Заголовок let titleAttrs: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 18, weight: .bold), .foregroundColor: UIColor.black ] "СЧЁТ №\(invoice.number)".draw(at: CGPoint(x: 40, y: 40), withAttributes: titleAttrs) // Таблица позиций drawInvoiceTable(invoice.items, in: ctx, startY: 120, pageWidth: 595.2) // Если контент не умещается — beginPage() для следующей страницы } } Рендеринг HTML → PDF. Сложную вёрстку (таблицы, колонки, изображения) проще описать HTML/CSS и сконвертировать через WKWebView. Пример конвертера:
class HTMLToPDFConverter: NSObject, WKNavigationDelegate { private var webView: WKWebView! func convert(html: String, completion: @escaping (Data?) -> Void) { webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 595, height: 842)) webView.navigationDelegate = self webView.loadHTMLString(html, baseURL: nil) self.completion = completion } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { let config = WKPDFConfiguration() config.rect = CGRect(origin: .zero, size: CGSize(width: 595, height: 842)) webView.createPDF(configuration: config) { result in self.completion?(try? result.get()) } } } WKWebView.createPDF (iOS 14+) — самый простой способ для сложной вёрстки. CSS @page правила управляют разрывами страниц (page-break-before: always), шрифтами, отступами.
PDFKit — для манипуляций с существующими PDF: добавить страницу, вставить аннотацию, слить несколько документов в один. PDFDocument, PDFPage, PDFAnnotation — простой API. Подробнее в PDFKit.
Android: PdfDocument и WebView
android.graphics.pdf.PdfDocument — нативная генерация через Canvas API, аналогично UIGraphicsPDFRenderer. Пример:
fun generatePdf(invoice: Invoice): ByteArray { val document = PdfDocument() val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create() // A4 val page = document.startPage(pageInfo) val canvas = page.canvas val paint = Paint().apply { textSize = 18f isFakeBoldText = true } canvas.drawText("СЧЁТ №${invoice.number}", 40f, 60f, paint) drawInvoiceTable(canvas, invoice.items, startY = 120f) document.finishPage(page) val output = ByteArrayOutputStream() document.writeTo(output) document.close() return output.toByteArray() } WebView → PDF. WebView.createPrintDocumentAdapter + PrintManager — стандартный путь на Android. Но это открывает диалог печати, а не возвращает ByteArray. Для программной генерации — WebView с PrintDocumentAdapter через reflection или сторонние библиотеки.
Альтернатива — iText 7 (AGPL, платная для коммерции) или Apache PDFBox (Apache 2.0). PdfBox на Android — порт с некоторыми ограничениями, но для большинства задач достаточен. OpenPDF (форк iText 2.x, LGPL) — хороший баланс функциональности и лицензии. Подробнее см. OpenPDF на GitHub.
Почему важно встраивать шрифты?
Стандартные системные шрифты в PDF могут не включаться корректно при открытии на другом устройстве. Правильный подход — embed шрифт в PDF. На iOS: UIFont(name:size:) с bundled TTF-файлом. На Android с iText/OpenPDF: PdfFont.createFont("assets/fonts/Roboto-Regular.ttf", PdfEncodings.IDENTITY_H, true) — true означает embed в документ.
Без embed кириллица часто отображается как квадратики — до 40% пользователей могут столкнуться с этой проблемой, если шрифт не встроен. Решение: всегда встраивайте шрифты.
Шаринг PDF из приложения
После генерации — UIActivityViewController (iOS) или FileProvider + ACTION_SEND Intent (Android). Для сохранения в Files/Downloads: UIDocumentPickerViewController (iOS) или MediaStore.Downloads URI (Android 10+). Просмотр без внешнего приложения: PDFView из PDFKit (iOS), PdfRenderer (Android) — постраничный рендеринг из файла в Bitmap.
Типичные проблемы и их решения
| Проблема | Решение |
|---|---|
| Кириллица отображается квадратиками | Встроить шрифт с помощью embed=true |
| Разрыв страницы посередине таблицы | Использовать CSS page-break-inside: avoid или Canvas с ручным контролем |
| PDF не открывается на старых устройствах | Генерировать в формате PDF 1.4 или ниже |
Что входит в работу?
- Анализ макета и требований к PDF-документу.
- Выбор оптимального подхода (Canvas / HTML / библиотека).
- Реализация генерации с учётом вёрстки, шрифтов, кириллицы.
- Интеграция сохранения и шаринга.
- Тестирование на реальных устройствах (5+ моделей).
- Передача исходного кода и документации.
Сроки и стоимость
2–3 рабочих дня для стандартных документов (счёт, акт, отчёт). Сложная вёрстка с изображениями, таблицами и многостраничным flow — до 5 дней. Стоимость рассчитывается индивидуально. Получите консультацию по оптимальному подходу для вашей задачи. Свяжитесь с нами — оценим ваш проект за 2 дня.







