diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf7fd49..d338ba73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log ## [Unreleased](https://github.com/techprimate/TPPDF/tree/HEAD) (2019-??-??) -[Full Changelog](https://github.com/techprimate/TPPDF/compare/1.5.0...HEAD) +[Full Changelog](https://github.com/techprimate/TPPDF/compare/1.5.1...HEAD) **Implemented enhancements:** @@ -11,6 +11,17 @@ **Merged pull requests:** +## [1.5.1](https://github.com/techprimate/TPPDF/tree/1.5.1) (2019-06-06) +[Full Changelog](https://github.com/techprimate/TPPDF/compare/1.5.0...1.5.1) + +**Implemented enhancements:** + +- Added merge/combining of multiple documents (Issue #67) + +**Closed issues:** + +- #67 + ## [1.5.0](https://github.com/techprimate/TPPDF/tree/1.5.0) (2019-06-05) [Full Changelog](https://github.com/techprimate/TPPDF/compare/1.4.1...1.5.0) diff --git a/Example/TPPDF/ViewController.swift b/Example/TPPDF/ViewController.swift index 8a25adb7..cb587fe0 100644 --- a/Example/TPPDF/ViewController.swift +++ b/Example/TPPDF/ViewController.swift @@ -17,14 +17,21 @@ class ViewController: UIViewController { } func generateTestPDF() { - let document = PDFDocument(format: .a4) + let document1 = PDFDocument(format: .a4) + for i in 0..<100 { + document1.add(text: "DOC 1 - \(i)") + } + + let document2 = PDFDocument(format: .a5) + for i in 0..<100 { + document2.add(text: "DOC 2 - \(i)") + } do { let startTime = CFAbsoluteTimeGetCurrent() * 1000 // Generate PDF file and save it in a temporary file. This returns the file URL to the temporary file - let url = try PDFGenerator.generateURL(document: document, filename: "Example.pdf", progress: { - (progressValue: CGFloat) in - print("progress: ", progressValue) + let url = try PDFGenerator.generateURL(documents: [document1, document2], filename: "Example.pdf", progress: { (docIndex: Int, progressValue: CGFloat, totalProgressValue: CGFloat) in + print("doc:", docIndex, "progress:", progressValue, "total:", totalProgressValue) }) let endTime = CFAbsoluteTimeGetCurrent() * 1000 print("Duration: \(floor(endTime - startTime)) ms") @@ -402,6 +409,11 @@ class ViewController: UIViewController { document.add(.headerRight, textObject: PDFSimpleText(text: "Header Right 2")) document.add(.headerRight, textObject: PDFSimpleText(text: "Header Right 3")) + // Create a second document and combine them + + let secondDocument = PDFDocument(format: .a6) + secondDocument.add(text: "This is a brand new document with a different format!") + /* ---- Execution Metrics ---- */ print("Preparation took: " + stringFromTimeInterval(interval: CFAbsoluteTimeGetCurrent() - startTime)) startTime = CFAbsoluteTimeGetCurrent() @@ -413,10 +425,11 @@ class ViewController: UIViewController { do { // Generate PDF file and save it in a temporary file. This returns the file URL to the temporary file - let url = try PDFGenerator.generateURL(document: document, filename: "Example.pdf", progress: { - (progressValue: CGFloat) in - print("progress: ", progressValue) - }, debug: true) + let url = try PDFGenerator.generateURL(documents: [document, secondDocument], + filename: "Example.pdf", + progress: { (docIndex: Int, progressValue: CGFloat, totalProgressValue: CGFloat) in + print("doc:", docIndex, "progress:", progressValue, "total:", totalProgressValue) + }) // Load PDF into a webview from the temporary file (self.view as? UIWebView)?.loadRequest(URLRequest(url: url)) diff --git a/README.md b/README.md index b9ea0290..48fb311d 100644 --- a/README.md +++ b/README.md @@ -772,6 +772,26 @@ try PDFGenerator.generate(document: document, to: url, progress: { progress in }, debug: false) ``` +#### Multiple Documents + +If you want to combine multiple `PDFDocument` into a single PDF file, use the alternative methods to the ones in the previous section, taking multiple `documents` as a parameter. + +The progress will now return three values: + + - the current document index + - the progress of the current document + - sum of total progress + +**Example:** + +```swift +let url = try PDFGenerator.generateURL(documents: [ + document1, document2 +], filename: "Example.pdf", progress: { (docIndex: Int, progressValue: CGFloat, totalProgressValue: CGFloat) in + print("doc:", docIndex, "progress:", progressValue, "total:", totalProgressValue) +}) +``` + #### Debug If you want to enable a debug overlay, set the flag `debug` in `PDFGenerator.generate(..)`, `PDFGenerator.generateURL(..)` or `PDFGenerator.generateData(..)` to `true` and it will add colored outlines of the elements in you document. diff --git a/Source/Layout/Groups/PDFGroupObject.swift b/Source/Layout/Groups/PDFGroupObject.swift index e013a27f..b23a3672 100644 --- a/Source/Layout/Groups/PDFGroupObject.swift +++ b/Source/Layout/Groups/PDFGroupObject.swift @@ -23,6 +23,11 @@ internal class PDFGroupObject: PDFObject { */ internal var objects: [(container: PDFGroupContainer, object: PDFObject)] + /** + TODO: Documentation + */ + internal var isFullPage: Bool + /** TODO: Documentation */ @@ -53,6 +58,7 @@ internal class PDFGroupObject: PDFObject { */ internal init(objects: [(container: PDFGroupContainer, object: PDFObject)], allowsBreaks: Bool, + isFullPage: Bool, backgroundColor: UIColor?, backgroundImage: PDFImage?, backgroundShape: PDFDynamicGeometryShape?, @@ -60,6 +66,7 @@ internal class PDFGroupObject: PDFObject { padding: UIEdgeInsets) { self.objects = objects self.allowsBreaks = allowsBreaks + self.isFullPage = isFullPage self.backgroundColor = backgroundColor self.backgroundImage = backgroundImage self.backgroundShape = backgroundShape @@ -97,7 +104,14 @@ internal class PDFGroupObject: PDFObject { result += calcResult } - self.frame = calculateFrame(objects: result) + if isFullPage { + self.frame = generator.document.layout.bounds.inset(by: UIEdgeInsets(top: generator.layout.margin.top, + left: generator.layout.margin.left, + bottom: generator.layout.margin.bottom, + right: generator.layout.margin.right)) + } else { + self.frame = calculateFrame(objects: result) + } generator.layout.heights.add(padding.bottom, to: container) generator.currentPadding = .zero @@ -120,7 +134,14 @@ internal class PDFGroupObject: PDFObject { result += try object.calculate(generator: generator, container: container.contentContainer) } - self.frame = calculateFrame(objects: result) + if isFullPage { + self.frame = generator.document.layout.bounds.inset(by: UIEdgeInsets(top: generator.layout.margin.top, + left: generator.layout.margin.left, + bottom: generator.layout.margin.bottom, + right: generator.layout.margin.right)) + } else { + self.frame = calculateFrame(objects: result) + } generator.layout.heights.add(padding.bottom, to: container) generator.currentPadding = .zero @@ -176,6 +197,7 @@ internal class PDFGroupObject: PDFObject { override internal var copy: PDFObject { return PDFGroupObject(objects: self.objects.map { ($0, $1.copy) }, allowsBreaks: self.allowsBreaks, + isFullPage: self.isFullPage, backgroundColor: self.backgroundColor, backgroundImage: self.backgroundImage?.copy, backgroundShape: self.backgroundShape, diff --git a/Source/Layout/Groups/PDFMasterGroup.swift b/Source/Layout/Groups/PDFMasterGroup.swift index 677edaa6..1f83ba10 100644 --- a/Source/Layout/Groups/PDFMasterGroup.swift +++ b/Source/Layout/Groups/PDFMasterGroup.swift @@ -13,10 +13,14 @@ import UIKit */ public class PDFMasterGroup: PDFGroup { + public var isFullPage: Bool + /** TODO: Documentation */ - public init() { + public init(isFullPage: Bool = false) { + self.isFullPage = isFullPage + super.init(allowsBreaks: false) } diff --git a/Source/Layout/PDFPageBreakObject.swift b/Source/Layout/PDFPageBreakObject.swift index e8cbe634..26423578 100644 --- a/Source/Layout/PDFPageBreakObject.swift +++ b/Source/Layout/PDFPageBreakObject.swift @@ -65,7 +65,7 @@ internal class PDFPageBreakObject: PDFObject { */ override internal func draw(generator: PDFGenerator, container: PDFContainer) throws { if !stayOnSamePage { - UIGraphicsBeginPDFPage() + UIGraphicsBeginPDFPageWithInfo(generator.document.layout.bounds, nil) generator.drawDebugPageOverlay() } } diff --git a/Source/PDFDocument+Objects.swift b/Source/PDFDocument+Objects.swift index 6ecc9abb..503cbaaf 100644 --- a/Source/PDFDocument+Objects.swift +++ b/Source/PDFDocument+Objects.swift @@ -343,6 +343,7 @@ public extension PDFDocument { func add(_ container: PDFContainer = PDFContainer.contentLeft, group: PDFGroup) { objects += [(container, PDFGroupObject(objects: group.objects, allowsBreaks: group.allowsBreaks, + isFullPage: false, backgroundColor: group.backgroundColor, backgroundImage: group.backgroundImage, backgroundShape: group.backgroundShape, @@ -353,9 +354,10 @@ public extension PDFDocument { /** TODO: Documentation */ - func set(master group: PDFGroup) { + func set(master group: PDFMasterGroup) { self.masterGroup = PDFGroupObject(objects: group.objects, allowsBreaks: group.allowsBreaks, + isFullPage: group.isFullPage, backgroundColor: group.backgroundColor, backgroundImage: group.backgroundImage, backgroundShape: group.backgroundShape, diff --git a/Source/PDFGenerator+Generation.swift b/Source/PDFGenerator+Generation.swift index 26d04791..ae0dce0b 100644 --- a/Source/PDFGenerator+Generation.swift +++ b/Source/PDFGenerator+Generation.swift @@ -24,7 +24,10 @@ extension PDFGenerator { - throws: PDFError */ - public static func generateURL(document: PDFDocument, filename: String, progress: ((CGFloat) -> Void)? = nil, debug: Bool = false) throws -> URL { + public static func generateURL(document: PDFDocument, + filename: String, + progress: ((CGFloat) -> Void)? = nil, + debug: Bool = false) throws -> URL { let name = filename.lowercased().hasSuffix(".pdf") ? filename : (filename + ".pdf") let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(name) let generator = PDFGenerator(document: document) @@ -39,6 +42,35 @@ extension PDFGenerator { return url } + /** + Generates PDF data and writes it to a temporary file. + + - parameter document: List of PDFDocument which should be concatenated and then converted into a PDF file. + - parameter filename: Name of temporary file. + - parameter progress: Optional closure for progress handling, showing the current document index, the current document progress and the total progress. + - parameter debug: Enables debugging + + - returns: URL to temporary file. + + - throws: PDFError + */ + public static func generateURL(documents: [PDFDocument], + filename: String, + info: PDFInfo = PDFInfo(), + progress: ((Int, CGFloat, CGFloat) -> Void)? = nil, + debug: Bool = false) throws -> URL { + assert(!documents.isEmpty, "At least one document is required!") + + let name = filename.lowercased().hasSuffix(".pdf") ? filename : (filename + ".pdf") + let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(name) + UIGraphicsBeginPDFContextToFile(url.path, documents.first?.layout.bounds ?? .zero, info.generate()) + + try process(documents: documents, progress: progress, debug: debug) + + UIGraphicsEndPDFContext() + return url + } + /** Generates PDF data and writes it to a temporary file. @@ -47,11 +79,12 @@ extension PDFGenerator { - parameter progress: Optional closure for progress handling. Parameter is between 0.0 and 1.0 - parameter debug: Enables debugging - - returns: URL to temporary file. - - throws: PDFError */ - public static func generate(document: PDFDocument, to url: URL, progress: ((CGFloat) -> Void)? = nil, debug: Bool = false) throws { + public static func generate(document: PDFDocument, + to url: URL, + progress: ((CGFloat) -> Void)? = nil, + debug: Bool = false) throws { let generator = PDFGenerator(document: document) generator.progressValue = 0 @@ -62,6 +95,28 @@ extension PDFGenerator { UIGraphicsEndPDFContext() } + /** + Generates PDF data and writes it to a temporary file. + + - parameter document: List of PDFDocuments which should be concatenated and then converted into a PDF file. + - parameter to url: URL where file should be saved. + - parameter progress: Optional closure for progress handling, showing the current document index, the current document progress and the total progress. + - parameter debug: Enables debugging + + - throws: PDFError + */ + public static func generate(documents: [PDFDocument], + to url: URL, + info: PDFInfo = PDFInfo(), + progress: ((Int, CGFloat, CGFloat) -> Void)? = nil, + debug: Bool = false) throws { + assert(!documents.isEmpty, "At least one document is required!") + + UIGraphicsBeginPDFContextToFile(url.path, documents.first?.layout.bounds ?? .zero, info.generate()) + try process(documents: documents, progress: progress, debug: debug) + UIGraphicsEndPDFContext() + } + /** Generates PDF data and returns it @@ -72,9 +127,10 @@ extension PDFGenerator { - returns: PDF Data - throws: PDFError - */ - public static func generateData(document: PDFDocument, progress: ((CGFloat) -> Void)? = nil, debug: Bool = false) throws -> Data { + public static func generateData(document: PDFDocument, + progress: ((CGFloat) -> Void)? = nil, + debug: Bool = false) throws -> Data { let data = NSMutableData() let generator = PDFGenerator(document: document) @@ -88,8 +144,61 @@ extension PDFGenerator { return data as Data } + /** + Generates PDF data and returns it + + - parameter documents: List of PDFDocument which should be concatenated and then converted into a PDF file. + - parameter progress: Optional closure for progress handling, showing the current document index, the current document progress and the total progress. + - parameter debug: Enables debugging + + - returns: PDF Data + + - throws: PDFError + */ + public static func generateData(documents: [PDFDocument], + info: PDFInfo = PDFInfo(), + progress: ((Int, CGFloat, CGFloat) -> Void)? = nil, + debug: Bool = false) throws -> Data { + assert(!documents.isEmpty, "At least one document is required!") + + let data = NSMutableData() + UIGraphicsBeginPDFContextToData(data, documents.first?.layout.bounds ?? .zero, info.generate()) + try process(documents: documents, progress: progress, debug: debug) + UIGraphicsEndPDFContext() + + return data as Data + } + // MARK: - INTERNAL FUNCS + /** + Processes multiple documents and renders them into the current PDFContext + + - parameter documents: List of PDFDocument to be processed + - parameter progress: Optional closure for progress handling, showing the current document index, the current document progress and the total progress. + - parameter debug: Enables debugging + + - throws: PDFError + */ + internal static func process(documents: [PDFDocument], progress: ((Int, CGFloat, CGFloat) -> Void)?, debug: Bool) throws { + let objCounts = documents.map { $0.objects.count } + let objSum = CGFloat(objCounts.reduce(0, +)) + let weights = objCounts.map { CGFloat($0) / objSum } + + var progressValues = [CGFloat](repeating: 0, count: documents.count) + for (idx, document) in documents.enumerated() { + let generator = PDFGenerator(document: document) + + generator.progressValue = 0 + generator.debug = debug + + try generator.generatePDFContext(progress: { value in + progressValues[idx] = value * weights[idx] + let totalProgress = progressValues.reduce(0, +) + progress?(idx, value, totalProgress) + }) + } + } /** Generate PDF Context from PDFCommands diff --git a/TPPDF.podspec b/TPPDF.podspec index ea740edf..979f8c7d 100644 --- a/TPPDF.podspec +++ b/TPPDF.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TPPDF' - s.version = '1.5.0' + s.version = '1.5.1' s.summary = 'TPPDF is a simple-to-use PDF builder for iOS' s.description = <<-DESC TPPDF is an object-based PDF builder, completely built in Swift.