├── Pod ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── SimplePDFLabel.swift │ ├── SimplePDFUtilities.swift │ └── SimplePDF.swift ├── _Pods.xcodeproj ├── Example ├── SimplePDF │ ├── Demo.png │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.xib │ └── ViewController.swift ├── SimplePDF.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── SimplePDF-Example.xcscheme │ └── project.pbxproj ├── SimplePDF.xcworkspace │ └── contents.xcworkspacedata ├── Podfile ├── Podfile.lock └── Tests │ ├── Info.plist │ └── Tests.swift ├── .gitignore ├── LICENSE ├── SimplePDFSwift.podspec └── README.md /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /Example/SimplePDF/Demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ishaq/SimplePDF/HEAD/Example/SimplePDF/Demo.png -------------------------------------------------------------------------------- /Example/SimplePDF.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/SimplePDF.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | 4 | target 'SimplePDF_Example' do 5 | pod 'SimplePDFSwift', :path => '../' 6 | end 7 | 8 | target 'SimplePDF_Tests' do 9 | pod 'SimplePDFSwift', :path => '../' 10 | 11 | pod 'Quick', '~> 0.10' 12 | pod 'Nimble', '~> 5.1' 13 | end 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.xccheckout 21 | 22 | # CocoaPods 23 | Pods 24 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (5.1.1) 3 | - Quick (0.10.0) 4 | - SimplePDFSwift (0.2.0) 5 | 6 | DEPENDENCIES: 7 | - Nimble (~> 5.1) 8 | - Quick (~> 0.10) 9 | - SimplePDFSwift (from `../`) 10 | 11 | EXTERNAL SOURCES: 12 | SimplePDFSwift: 13 | :path: ../ 14 | 15 | SPEC CHECKSUMS: 16 | Nimble: 415e3aa3267e7bc2c96b05fa814ddea7bb686a29 17 | Quick: 5d290df1c69d5ee2f0729956dcf0fd9a30447eaa 18 | SimplePDFSwift: dddcf53006d378ef81c108c4eb69ef1ce9f15451 19 | 20 | PODFILE CHECKSUM: 1969d8018ae45518cae8f1968618b8b86c1d2d9b 21 | 22 | COCOAPODS: 1.1.1 23 | -------------------------------------------------------------------------------- /Pod/Classes/SimplePDFLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFLabel.swift 3 | // 4 | // Created by Muhammad Ishaq on 22/03/2015. 5 | // 6 | 7 | import UIKit 8 | 9 | class SimplePDFLabel: UILabel { 10 | 11 | override func drawText(in rect: CGRect) { 12 | let isPDF = !UIGraphicsGetPDFContextBounds().isEmpty 13 | let layer = self.layer 14 | if(!layer.shouldRasterize && isPDF && (self.backgroundColor == nil || self.backgroundColor!.cgColor.alpha == 0)) { 15 | self.draw(self.bounds) 16 | } 17 | else { 18 | super.drawText(in: rect) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Example/SimplePDF/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Muhammad Ishaq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Example/SimplePDF/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | // https://github.com/Quick/Quick 2 | 3 | import Quick 4 | import Nimble 5 | import SimplePDFSwift 6 | 7 | class TableOfContentsSpec: QuickSpec { 8 | override func spec() { 9 | describe("these will fail") { 10 | 11 | it("can do maths") { 12 | expect(1) == 2 13 | } 14 | 15 | it("can read") { 16 | expect("number") == "string" 17 | } 18 | 19 | it("will eventually fail") { 20 | expect("time").toEventually( equal("done") ) 21 | } 22 | 23 | context("these will pass") { 24 | 25 | it("can do maths") { 26 | expect(23) == 23 27 | } 28 | 29 | it("can read") { 30 | expect("🐮") == "🐮" 31 | } 32 | 33 | it("will eventually pass") { 34 | var time = "passing" 35 | 36 | DispatchQueue.main.async { 37 | time = "done" 38 | } 39 | 40 | waitUntil { done in 41 | Thread.sleep(forTimeInterval: 0.5) 42 | expect(time) == "done" 43 | 44 | done() 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SimplePDFSwift.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint SimplePDF.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "SimplePDFSwift" 11 | s.version = "0.2.1" 12 | s.summary = "A Swift class to help generate simple PDF documents with page numbers and Table of Contents." 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | s.description = <<-DESC 20 | SimplePDF is a Swift class to create PDF documents with page numbers and table of contents. SimplePDF generated document may have: 21 | 22 | * Headings (H1 - H6) and Body Text. 23 | * Images (with captions), multiple images per row. 24 | * Multi column text (can be used for borderless tables too) 25 | * UIView instances (good for cover pages, etc) 26 | * Any attributed string 27 | DESC 28 | 29 | s.homepage = "https://github.com/ishaq/SimplePDF" 30 | # s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2" 31 | s.license = 'MIT' 32 | s.author = { "Muhammad Ishaq" => "ishaq@involution.co" } 33 | s.source = { :git => "https://github.com/ishaq/SimplePDF.git", :tag => s.version.to_s } 34 | # s.social_media_url = 'https://twitter.com/' 35 | 36 | s.platform = :ios, '8.0' 37 | s.requires_arc = true 38 | 39 | s.source_files = 'Pod/Classes/**/*' 40 | # s.resource_bundles = { 41 | # 'SimplePDF' => ['Pod/Assets/*.png'] 42 | # } 43 | 44 | # s.public_header_files = 'Pod/Classes/**/*.h' 45 | # s.frameworks = 'UIKit', 'MapKit' 46 | # s.dependency 'AFNetworking', '~> 2.3' 47 | end 48 | -------------------------------------------------------------------------------- /Example/SimplePDF/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SimplePDF 4 | // 5 | // Created by Muhammad Ishaq on 04/07/2016. 6 | // Copyright (c) 2016 Muhammad Ishaq. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/SimplePDF/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/SimplePDF/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Pod/Classes/SimplePDFUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePDFUtilities.swift 3 | // 4 | // Created by Muhammad Ishaq on 22/03/2015 5 | // 6 | 7 | import Foundation 8 | import ImageIO 9 | import UIKit 10 | 11 | class SimplePDFUtilities { 12 | 13 | class func getApplicationInfoDictionary() -> NSDictionary { 14 | let infoDictionary = NSMutableDictionary() 15 | infoDictionary.addEntries(from: Bundle.main.infoDictionary!) 16 | if let localizedInfoDictionary = Bundle.main.localizedInfoDictionary { 17 | infoDictionary.addEntries(from: localizedInfoDictionary) 18 | } 19 | return infoDictionary 20 | } 21 | 22 | class func getApplicationVersion() -> String { 23 | let dictionary = getApplicationInfoDictionary() 24 | 25 | let shortVersionString = dictionary["CFBundleShortVersionString"] as? String ?? "" 26 | let build = dictionary["CFBundleVersion"] as? String ?? "" 27 | return "\(shortVersionString) Build: \(build)" 28 | } 29 | 30 | class func getApplicationName() -> String { 31 | let dictionary = getApplicationInfoDictionary() 32 | 33 | let name = dictionary["CFBundleName"] as! NSString 34 | 35 | return name as String 36 | } 37 | 38 | class func pathForTmpFile(_ fileName: String) -> String { 39 | let tmpDirPath = NSTemporaryDirectory() as NSString 40 | let path = tmpDirPath.appendingPathComponent(fileName) 41 | return path 42 | } 43 | 44 | class func renameFilePathToPreventNameCollissions(_ path: NSString) -> String { 45 | let fileManager = FileManager() 46 | 47 | // append a postfix if file name is already taken 48 | var postfix = 0 49 | var newPath = path 50 | while(fileManager.fileExists(atPath: newPath as String)) { 51 | postfix += 1 52 | 53 | let pathExtension = path.pathExtension 54 | newPath = path.deletingPathExtension as NSString 55 | newPath = newPath.appending(" \(postfix)") as NSString 56 | newPath = newPath.appendingPathExtension(pathExtension)! as NSString 57 | } 58 | 59 | return newPath as String 60 | } 61 | 62 | class func getImageProperties(_ imagePath: String) -> NSDictionary { 63 | let imageURL = URL(fileURLWithPath: imagePath) 64 | guard let imageSourceRef = CGImageSourceCreateWithURL(imageURL as CFURL, nil) else { 65 | return NSDictionary() 66 | } 67 | 68 | let propertiesAsCFDictionary = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, nil) 69 | // translating it to an optional NSDictionary (instead of as? operator) because: 70 | // http://stackoverflow.com/questions/32716146/cfdictionary-wont-bridge-to-nsdictionary-swift-2-0-ios9 71 | guard let propertiesAsNSDictionary = propertiesAsCFDictionary as NSDictionary? else { 72 | return NSDictionary() 73 | } 74 | 75 | return propertiesAsNSDictionary 76 | } 77 | 78 | class func getNumericListAlphabeticTitleFromInteger(_ value: Int) -> String { 79 | let base:Int = 26 80 | let unicodeLetterA :UnicodeScalar = "\u{0061}" // a 81 | var mutableValue = value 82 | var result = "" 83 | repeat { 84 | let remainder = mutableValue % base 85 | mutableValue = mutableValue - remainder 86 | mutableValue = mutableValue / base 87 | let unicodeChar = UnicodeScalar(remainder + Int(unicodeLetterA.value)) 88 | result = String(describing: unicodeChar) + result 89 | 90 | } while mutableValue > 0 91 | 92 | return result 93 | } 94 | 95 | class func generateThumbnail(_ imageURL: URL, size: CGSize, callback: @escaping (_ thumbnail: UIImage, _ fromURL: URL, _ size: CGSize) -> Void) { 96 | DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async(execute: { () -> Void in 97 | if let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, nil) { 98 | let options = [ 99 | kCGImageSourceThumbnailMaxPixelSize as String: max(size.width, size.height), 100 | kCGImageSourceCreateThumbnailFromImageIfAbsent as String: true 101 | ] as [String : Any] 102 | 103 | if let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary?) { 104 | let thumbnail = UIImage(cgImage: cgImage) 105 | DispatchQueue.main.async(execute: { () -> Void in 106 | callback(thumbnail, imageURL, size) 107 | }) 108 | } 109 | } 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Example/SimplePDF.xcodeproj/xcshareddata/xcschemes/SimplePDF-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimplePDF 2 | 3 | 4 | 8 | 9 | **This class is no longer maintained by me. If you want to take over, send me an email!** 10 | 11 | SimplePDF is a Swift class that lets you create simple PDF documents with page numbers and table of contents. The code is a rough implementation of [Builder](https://en.wikipedia.org/wiki/Builder_pattern) design pattern. See the demo project for usage example. 12 | 13 | ## Usage 14 | 15 | To run the example project, clone the repo, and run `pod install` from the Example directory first. Or run `pod try SimplePDFSwift`. 16 | 17 | :warning: **Important: Pod for this library is called `SimplePDFSwift`, Please note that pod named `SimplePDF` is a different library (It is available [here](https://github.com/nrewik/SimplePDF) if you'd like to try it).** :warning: 18 | 19 | ## Requirements 20 | 21 | SimplePDF is written in Swift 3.0 as of version 0.2.1. Therefore you need Xcode 8.0 for development. You can target iOS 8.0 and later. 22 | 23 | To use SimplePDF on previous versions of Swift, you can use a version earlier than **0.2.1**. 24 | 25 | 26 | ## Installation 27 | 28 | SimplePDF is available through [CocoaPods](http://cocoapods.org). To install 29 | it, simply add the following line to your Podfile: 30 | 31 | ```ruby 32 | pod "SimplePDFSwift" 33 | ``` 34 | 35 | ### Features 36 | Although SimplePDF can only generate simple PDF documents, It can be used in a variety of use cases. It allows you to add: 37 | 38 | * Headings (H1 - H6) and Body Text. Their formatting can be customized by passing a subclass of `DefaultTextFormatter` to SimplePDF `init` method 39 | * Images (with captions) 40 | * Add text to multiple columns, it can also be used to create borderless tables 41 | * Headers/Footers (with page number and pages-count) 42 | * UIView instances (You can design a UIView in a nib or in a storyboard and add it as a page to the PDF. This can be useful to add, for example, a cover page with company logo, etc.) 43 | 44 | In addition to predefined headings and body text formats, you can also add any `NSAttributedString` to the pdf, however it will not be included in Table of Contents. Table of Contents only takes into account the content added through `addH1` ... `addH6` functions. 45 | 46 | ## Getting Started 47 | After installation from CocoaPods, import the module (`import SimplePDFSwift`). A typical usage would look like: 48 | 49 | ```swift 50 | import SimplePDFSwift 51 | 52 | // Initialize 53 | let pdf = SimplePDF(pdfTitle: "Simple PDF Demo", authorName: "Muhammad Ishaq") 54 | 55 | // add some content 56 | pdf.addH1("Level 2 Heading") 57 | pdf.addBodyText("Some body text, probably a long string with multiple paras") 58 | pdf.addH2("Level 2 Heading") 59 | pdf.addBodyText("Lorem ipsum dolor sit amet...") 60 | 61 | // add an image 62 | let imagePath = NSBundle.mainBundle().pathForResource("Demo", ofType: "png")! 63 | let imageCaption = "fig 1: Lorem ipsum dolor sit amet" 64 | pdf.addImages([imagePath], imageCaptions: [imageCaption], imagesPerRow: 1) 65 | 66 | // Configure headers/footers (discussed below) 67 | // ... 68 | 69 | // Write PDF 70 | // Note that the tmpPDFPath is path to a temporary file, it needs to be saved somewhere 71 | let tmpPDFPath = pdf.writePDFWithTableOfContents() 72 | ``` 73 | 74 | If you don't want a table of contents to be generated, instead of `writePDFWithTableOfContents`, you can call `writePDFWithoutTableOfContents()`. 75 | 76 | ### Adding Headers/Footers 77 | 78 | There are two types of Headers/Footers. 79 | 80 | 1. **Text** This is added using `HeaderFooterText` instance, any new line characters result in multiline header (or footer). This can, for example, be the name of the author and document creation date, or it can be the page number e.g. "Page 1 of 12" or any other text. 81 | 82 | 2. **Image:** This is added using `HeaderFooterImage` instance and can only be a single image (e.g. an icon or logo). 83 | 84 | **Alignment:** Header/Footer can have _Left_, _Center_ or _Right_ alignment, It will be added to the corresponding location on top/bottom of the page. 85 | 86 | p**Page Number & Pages Count:** In a text header/footer, occurrences of `SimplePDF.pageNumberPlaceholder` are replaced with the current page number and occurrences of `SimplePDF.pagesCountPlaceholder` are replaced with pages-count. 87 | 88 | The `pageRange` attribute controls which pages the header/footer appears on. `pageRange` is an `NSRange` instance, `pageRange.location` specifies zero based page index where the header/footer would first appear. `pageRange.length` specifies how many pages it would appear on (starting at `pageRange.location`). 89 | 90 | Here's how a multiline header (or footer) could be added (in this case it is a header on the left and first appears on the second page): 91 | 92 | ```swift 93 | // Variables to format the header string 94 | let regularFont = UIFont.systemFontOfSize(8) 95 | let boldFont = UIFont.boldSystemFontOfSize(8) 96 | let leftAlignment = NSMutableParagraphStyle() 97 | leftAlignment.alignment = NSTextAlignment.Left 98 | let dateFormatter = NSDateFormatter() 99 | dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle 100 | dateFormatter.timeStyle = NSDateFormatterStyle.MediumStyle 101 | let dateString = dateFormatter.stringFromDate(NSDate()) 102 | 103 | // Create the attributed string for header 104 | let leftHeaderString = "Author: Muhammad Ishaq\nDate/Time: \(dateString)" 105 | let leftHeaderAttrString = NSMutableAttributedString(string: leftHeaderString) 106 | leftHeaderAttrString.addAttribute(NSParagraphStyleAttributeName, value: leftAlignment, range: NSMakeRange(0, leftHeaderAttrString.length)) 107 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: regularFont, range: NSMakeRange(0, leftHeaderAttrString.length)) 108 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: boldFont, range: leftHeaderAttrString.mutableString.rangeOfString("Author:")) 109 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: boldFont, range: leftHeaderAttrString.mutableString.rangeOfString("Date/Time:")) 110 | 111 | // Create the header 112 | // location of pageRange is 1, so it skips page 0 i.e. the first page and appears on second page 113 | let header = SimplePDF.HeaderFooterText(type: .Header, pageRange: NSMakeRange(1, Int.max), attributedString: leftHeaderAttrString) 114 | pdf.headerFooterTexts.append(header) 115 | ``` 116 | 117 | Here's how a logo could be added 118 | 119 | ```swift 120 | // add a logo to the header, on the right 121 | let logoPath = NSBundle.mainBundle().pathForResource("Demo", ofType: "png") 122 | // location of pageRange is 1, so it skips page 0 i.e. the first page 123 | // NOTE: we can specify either the image (UIImage instance) or its path 124 | let rightLogo = SimplePDF.HeaderFooterImage(type: .Header, pageRange: NSMakeRange(1, Int.max), 125 | imagePath: logoPath!, image:nil, imageHeight: 35, alignment: .Right) 126 | pdf.headerFooterImages.append(rightLogo) 127 | ``` 128 | 129 | And here's how page number with pages-count could be added 130 | 131 | ```swift 132 | // add page numbers to the footer (center aligned) 133 | let centerAlignment = NSMutableParagraphStyle() 134 | centerAlignment.alignment = .Center 135 | let footerString = NSMutableAttributedString(string: "\(SimplePDF.pageNumberPlaceholder) of \(SimplePDF.pagesCountPlaceholder)") 136 | footerString.addAttribute(NSParagraphStyleAttributeName, value: centerAlignment, range: NSMakeRange(0, footerString.length)) 137 | // location of pageRange is 1, so it skips page 0 i.e. the first page 138 | let footer = SimplePDF.HeaderFooterText(type: .Footer, pageRange: NSMakeRange(1, Int.max), attributedString: footerString) 139 | pdf.headerFooterTexts.append(footer) 140 | ``` 141 | 142 | ### Adding a View 143 | You can call `addView(view)` to render UIView instances to a PDF page. The passed view will be rendered new PDF page. This is mostly useful to design cover pages. A view is always added to its own page. It starts a new page if required, and any content added after it appears on the next page. 144 | 145 | Here's how you can design a cover page with a UIView (or a subclass) 146 | 147 | 1. Create a nib with the same dimensions as PDF page (e.g. A4 page is 595x842, you can lookup other dimensions in `PageSize` enum). 148 | 2. Optional: If you want the labels to appear as text (instead of bitmaps) in the PDF, all the labels in the view should have their class set to `SimplePDFLabel` (or a subclass of it). 149 | 3. Load the view from the nib and add it to pdf 150 | 151 | ```swift 152 | let coverPage = NSBundle.mainBundle().loadNibNamed("PDFCoverPage", owner: self, options: nil).first as PDFCoverPage 153 | pdf.addView(coverPage) 154 | ``` 155 | 156 | **Note:** Please note that if you use the above method to render a view to PDF, AutoLayout will **not** be run on it, If your view doesn't rely on AutoLayout, you don't need to worry about anything. However, if your view uses AutoLayout to correctly position elements, you **have to** add it to the active view hierarchy. You can add to the view hierarchy off-screen, then call `pdf.addView(view)` to render it to PDF. But now the view would render as _bitmap_. This means any labels will not be selectable as text and they would lose quality (being bitmaps) if you zoom in. 157 | 158 | ### Customizing Heading and Body Text Formatting 159 | To customize the formatting used in `addH1` ... `addH6` and `addBodyText` functions, you need to: 160 | 161 | 1. Subclass `DefaultTextFormatter` and override appropriate methods 162 | 2. Pass instance of your custom subclass to `SimplePDF`'s `init`. 163 | 164 | SimplePDF will now use your subclass instead of `DefaultTextFormatter` to format headings and body text. 165 | 166 | ### Other Tasks 167 | * You can add attributed strings with `addAttributedString(attrString)`. Note, however, that these strings will not appear in table of contents (no matter how big the rendered text is). 168 | * You can add text to multiple columns with `addAttributedStringsToColumns(columnWidths: [CGFloat], strings: [NSAttributedString])`. This can also be used to create borderless tables. Passing empty string keeps corresponding column empty. 169 | * `addImages(imagePaths:[String], imageCaptions: [String], imagesPerRow:Int = 3)` adds images to pdf. It resizes the images uniformly to fit `imagesPerRow` images in available page width. Passing nil for image (and empty string for caption) keeps corresponding column empty. 170 | * `addImagesRow(imagePaths: [String], imageCaptions: [NSAttributedString], columnWidths: [CGFloat])` adds a single row of images using column widths specified. Passing nil for image (and empty string for caption) keeps corresponding column empty. 171 | 172 | ## Authors 173 | 174 | Muhammad Ishaq (ishaq@ishaq.pk), Martin Stemmle (hi@martn.st) 175 | 176 | 177 | ## License 178 | 179 | SimplePDF is available under the MIT license. See the LICENSE file for more info. 180 | -------------------------------------------------------------------------------- /Example/SimplePDF/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SimplePDF 4 | // 5 | // Created by Muhammad Ishaq on 04/07/2016. 6 | // Copyright (c) 2016 Muhammad Ishaq. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SimplePDFSwift 11 | 12 | class ViewController: UIViewController, UIDocumentInteractionControllerDelegate { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view, typically from a nib. 17 | } 18 | 19 | override func didReceiveMemoryWarning() { 20 | super.didReceiveMemoryWarning() 21 | // Dispose of any resources that can be recreated. 22 | } 23 | 24 | @IBAction func generateAndOpenPDF(_ sender: AnyObject) { 25 | DispatchQueue.global().async { 26 | self.generateAndOpenPDFHandler() 27 | } 28 | } 29 | 30 | // MARK: - UIDocumentInteractionControllerDelegate 31 | func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { 32 | return self 33 | } 34 | 35 | // MARK: - Private 36 | fileprivate func generateAndOpenPDFHandler() { 37 | let pdf = SimplePDF(pdfTitle: "Simple PDF Demo", authorName: "Muhammad Ishaq") 38 | 39 | self.addDocumentCover(pdf) 40 | self.addDocumentContent(pdf) 41 | self.addHeadersFooters(pdf) 42 | 43 | // here we may want to save the pdf somewhere or show it to the user 44 | let tmpPDFPath = pdf.writePDFWithTableOfContents() 45 | 46 | // open the generated PDF 47 | DispatchQueue.main.async(execute: { () -> Void in 48 | let pdfURL = URL(fileURLWithPath: tmpPDFPath) 49 | let interactionController = UIDocumentInteractionController(url: pdfURL) 50 | interactionController.delegate = self 51 | interactionController.presentPreview(animated: true) 52 | }) 53 | } 54 | 55 | fileprivate func addDocumentCover(_ pdf: SimplePDF) { 56 | // Cover Page can be designed in a nib (.xib) and added to pdf via `pdf.addView()` call. 57 | // 58 | // Here's how you can design a cover page with using a UIView (sample applies to any other view that you want to add to pdf) 59 | // 1. Create a nib with the same dimensions as PDF page (e.g. A4 page is 595x842) 60 | // 2. All the labels in the view should have their class set to `SimplePDFLabel` (or a subclass of it) 61 | // 3. Load the view from the nib and add it to pdf 62 | // ``` 63 | // // ... 64 | // let coverPage = NSBundle.mainBundle().loadNibNamed("PDFCoverPage", owner: self, options: nil).first as PDFCoverPage 65 | // pdf.addView(coverPage) 66 | // ``` 67 | // 68 | // NOTE: 69 | // Please note that if you use the above method to render a view to PDF, AutoLayout will *not* be run on it, If your view doesn't rely on 70 | // autolayout e.g. say it's a simple table, you don't need to worry about anything. 71 | // 72 | // However, if your view uses AutoLayout to correctly position elements, you *have to* add it to the active view hierarchy. You can add to the 73 | // view hierarchy off-screen, then call `pdf.addView()` to render it to PDF. The catch here is that now the view would render as *bitmap*. This means 74 | // any labels will not be selectable as text and they would lose quality if you zoom in (because they are bitmaps). 75 | // 76 | 77 | 78 | // NOTE: we manually format document title and use `addAttributedString` instead of, say, `addH1` because we don't want it in the TOC 79 | let documentTitle = NSMutableAttributedString(string: "Demo PDF") 80 | let titleFont = UIFont.boldSystemFont(ofSize: 48) 81 | let paragraphAlignment = NSMutableParagraphStyle() 82 | paragraphAlignment.alignment = .center 83 | let titleRange = NSMakeRange(0, documentTitle.length) 84 | documentTitle.addAttribute(NSFontAttributeName, value: titleFont, range: titleRange) 85 | documentTitle.addAttribute(NSParagraphStyleAttributeName, value: paragraphAlignment, range: titleRange) 86 | pdf.addAttributedString(documentTitle) 87 | 88 | // we don't want anymore text on this page, so we force a page break 89 | pdf.startNewPage() 90 | } 91 | 92 | fileprivate func addDocumentContent(_ pdf: SimplePDF) { 93 | pdf.addH2("Level 2 Heading") 94 | pdf.addBodyText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec enim est. Phasellus eu lacus ac ex facilisis porta eu ac nisi. Ut ullamcorper id justo vel lobortis. Cras sed egestas elit, malesuada maximus metus. Mauris faucibus, metus et interdum feugiat, mauris felis varius lacus, porta semper ipsum eros eget massa. Fusce et diam ac lacus bibendum rutrum ac nec neque. Proin rutrum nisl nec vestibulum commodo. Donec eu dolor quis sapien lobortis elementum. Ut tincidunt justo at mauris lobortis placerat. Nam tristique ornare luctus. Donec eu pretium sapien. Pellentesque venenatis eros nulla, eget tincidunt mauris tempor eget. In egestas orci a sem congue semper.") 95 | 96 | let imagePath = Bundle.main.path(forResource: "Demo", ofType: "png")! 97 | let imageCaption = "fig 1: Lorem ipsum dolor sit amet" 98 | pdf.addImages([imagePath], imageCaptions: [imageCaption], imagesPerRow: 1) 99 | 100 | pdf.addBodyText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec enim est. Phasellus eu lacus ac ex facilisis porta eu ac nisi. Ut ullamcorper id justo vel lobortis. Cras sed egestas elit, malesuada maximus metus. Mauris faucibus, metus et interdum feugiat, mauris felis varius lacus, porta semper ipsum eros eget massa. Fusce et diam ac lacus bibendum rutrum ac nec neque. Proin rutrum nisl nec vestibulum commodo. Donec eu dolor quis sapien lobortis elementum. Ut tincidunt justo at mauris lobortis placerat. Nam tristique ornare luctus. Donec eu pretium sapien. Pellentesque venenatis eros nulla, eget tincidunt mauris tempor eget. In egestas orci a sem congue semper.") 101 | 102 | for i in 0..<2 { 103 | pdf.addH3("Level 3 Heading \(i)") 104 | pdf.addBodyText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec enim est. Phasellus eu lacus ac ex facilisis porta eu ac nisi. Ut ullamcorper id justo vel lobortis. Cras sed egestas elit, malesuada maximus metus. Mauris faucibus, metus et interdum feugiat, mauris felis varius lacus, porta semper ipsum eros eget massa. Fusce et diam ac lacus bibendum rutrum ac nec neque. Proin rutrum nisl nec vestibulum commodo. Donec eu dolor quis sapien lobortis elementum. Ut tincidunt justo at mauris lobortis placerat. Nam tristique ornare luctus. Donec eu pretium sapien. Pellentesque venenatis eros nulla, eget tincidunt mauris tempor eget. In egestas orci a sem congue semper.") 105 | for j in 0..<3 { 106 | pdf.addH4("Level 4 Heading \(j)") 107 | pdf.addBodyText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec enim est. Phasellus eu lacus ac ex facilisis porta eu ac nisi. Ut ullamcorper id justo vel lobortis. Cras sed egestas elit, malesuada maximus metus. Mauris faucibus, metus et interdum feugiat, mauris felis varius lacus, porta semper ipsum eros eget massa. Fusce et diam ac lacus bibendum rutrum ac nec neque. Proin rutrum nisl nec vestibulum commodo. Donec eu dolor quis sapien lobortis elementum. Ut tincidunt justo at mauris lobortis placerat. Nam tristique ornare luctus. Donec eu pretium sapien. Pellentesque venenatis eros nulla, eget tincidunt mauris tempor eget. In egestas orci a sem congue semper.") 108 | for k in 0..<3 { 109 | pdf.addH5("Level 5 Heading \(k)") 110 | pdf.addBodyText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec enim est. Phasellus eu lacus ac ex facilisis porta eu ac nisi. Ut ullamcorper id justo vel lobortis. Cras sed egestas elit, malesuada maximus metus. Mauris faucibus, metus et interdum feugiat, mauris felis varius lacus, porta semper ipsum eros eget massa. Fusce et diam ac lacus bibendum rutrum ac nec neque. Proin rutrum nisl nec vestibulum commodo. Donec eu dolor quis sapien lobortis elementum. Ut tincidunt justo at mauris lobortis placerat. Nam tristique ornare luctus. Donec eu pretium sapien. Pellentesque venenatis eros nulla, eget tincidunt mauris tempor eget. In egestas orci a sem congue semper.") 111 | 112 | pdf.addImages([imagePath, imagePath, imagePath, imagePath], imageCaptions: [imageCaption, imageCaption, imageCaption, imageCaption], imagesPerRow: 3, spacing: 10, padding: 5) 113 | } 114 | } 115 | } 116 | } 117 | 118 | fileprivate func addHeadersFooters(_ pdf: SimplePDF) { 119 | let regularFont = UIFont.systemFont(ofSize: 8) 120 | let boldFont = UIFont.boldSystemFont(ofSize: 8) 121 | let leftAlignment = NSMutableParagraphStyle() 122 | leftAlignment.alignment = NSTextAlignment.left 123 | 124 | let dateFormatter = DateFormatter() 125 | dateFormatter.dateStyle = DateFormatter.Style.medium 126 | dateFormatter.timeStyle = DateFormatter.Style.medium 127 | let dateString = dateFormatter.string(from: Date()) 128 | 129 | // add some document information to the header, on left 130 | let leftHeaderString = "Author: Muhammad Ishaq\nDate/Time: \(dateString)" 131 | let leftHeaderAttrString = NSMutableAttributedString(string: leftHeaderString) 132 | leftHeaderAttrString.addAttribute(NSParagraphStyleAttributeName, value: leftAlignment, range: NSMakeRange(0, leftHeaderAttrString.length)) 133 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: regularFont, range: NSMakeRange(0, leftHeaderAttrString.length)) 134 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: boldFont, range: leftHeaderAttrString.mutableString.range(of: "Author:")) 135 | leftHeaderAttrString.addAttribute(NSFontAttributeName, value: boldFont, range: leftHeaderAttrString.mutableString.range(of: "Date/Time:")) 136 | let header = SimplePDF.HeaderFooterText(type: .header, pageRange: NSMakeRange(1, Int.max), attributedString: leftHeaderAttrString) 137 | pdf.headerFooterTexts.append(header) 138 | 139 | // add a logo to the header, on right 140 | let logoPath = Bundle.main.path(forResource: "Demo", ofType: "png") 141 | // NOTE: we can specify either the image or its path 142 | let rightLogo = SimplePDF.HeaderFooterImage(type: .header, pageRange: NSMakeRange(1, Int.max), 143 | imagePath: logoPath!, image:nil, imageHeight: 35, alignment: .right) 144 | pdf.headerFooterImages.append(rightLogo) 145 | 146 | // add page numbers to the footer (center aligned) 147 | let centerAlignment = NSMutableParagraphStyle() 148 | centerAlignment.alignment = .center 149 | let footerString = NSMutableAttributedString(string: "\(SimplePDF.pageNumberPlaceholder) of \(SimplePDF.pagesCountPlaceholder)") 150 | footerString.addAttribute(NSParagraphStyleAttributeName, value: centerAlignment, range: NSMakeRange(0, footerString.length)) 151 | let footer = SimplePDF.HeaderFooterText(type: .footer, pageRange: NSMakeRange(1, Int.max), attributedString: footerString) 152 | pdf.headerFooterTexts.append(footer) 153 | 154 | // add a link to your app may be 155 | let link = NSMutableAttributedString(string: "http://ishaq.pk/") 156 | link.addAttribute(NSParagraphStyleAttributeName, value: leftAlignment, range: NSMakeRange(0, link.length)) 157 | let appLinkFooter = SimplePDF.HeaderFooterText(type: .footer, pageRange: NSMakeRange(1, Int.max), attributedString: link) 158 | pdf.headerFooterTexts.append(appLinkFooter) 159 | 160 | // NOTE: we can specify either the image or its path 161 | let footerImage = SimplePDF.HeaderFooterImage(type: .footer, pageRange: NSMakeRange(1, Int.max), 162 | imagePath: logoPath!, image:nil, imageHeight: 20, alignment: .right) 163 | pdf.headerFooterImages.append(footerImage) 164 | } 165 | } 166 | 167 | -------------------------------------------------------------------------------- /Example/SimplePDF.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0143B02A7600C097D646CCBC /* Pods_SimplePDF_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D27C11E0F83157C81BD71DD /* Pods_SimplePDF_Tests.framework */; }; 11 | 04C45F3B30943FB63B203FC0 /* Pods_SimplePDF_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AB1F9B3DF65A03567EDE17 /* Pods_SimplePDF_Example.framework */; }; 12 | 57E4A0391CB6A0B000EBDFEF /* Demo.png in Resources */ = {isa = PBXBuildFile; fileRef = 57E4A0381CB6A0B000EBDFEF /* Demo.png */; }; 13 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 14 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 15 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 16 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 17 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 18 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 27 | remoteInfo = SimplePDF; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1AC8B89E3D73A8D2544B9387 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 33 | 2D27C11E0F83157C81BD71DD /* Pods_SimplePDF_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimplePDF_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 3B6DD16E263E3EA17BABFB57 /* Pods-SimplePDF_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimplePDF_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SimplePDF_Tests/Pods-SimplePDF_Tests.release.xcconfig"; sourceTree = ""; }; 35 | 3BB44BCB67803C6A1698D498 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 36 | 556099EE118477E831FBE9B4 /* Pods-SimplePDF_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimplePDF_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SimplePDF_Example/Pods-SimplePDF_Example.debug.xcconfig"; sourceTree = ""; }; 37 | 57E4A0381CB6A0B000EBDFEF /* Demo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Demo.png; sourceTree = ""; }; 38 | 607FACD01AFB9204008FA782 /* SimplePDF_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimplePDF_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 44 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 45 | 607FACE51AFB9204008FA782 /* SimplePDF_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimplePDF_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 48 | 65AB1F9B3DF65A03567EDE17 /* Pods_SimplePDF_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimplePDF_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | A247239FC55D62CD18E5A79E /* Pods-SimplePDF_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimplePDF_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-SimplePDF_Example/Pods-SimplePDF_Example.release.xcconfig"; sourceTree = ""; }; 50 | B6B8A5EFE2A1705B3145BBAD /* Pods-SimplePDF_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimplePDF_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SimplePDF_Tests/Pods-SimplePDF_Tests.debug.xcconfig"; sourceTree = ""; }; 51 | E3ACCBF4822A73427213F8A9 /* SimplePDFSwift.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SimplePDFSwift.podspec; path = ../SimplePDFSwift.podspec; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 04C45F3B30943FB63B203FC0 /* Pods_SimplePDF_Example.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 0143B02A7600C097D646CCBC /* Pods_SimplePDF_Tests.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 607FACC71AFB9204008FA782 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 78 | 607FACD21AFB9204008FA782 /* Example for SimplePDF */, 79 | 607FACE81AFB9204008FA782 /* Tests */, 80 | 607FACD11AFB9204008FA782 /* Products */, 81 | 83C48FFABC027AD69AEFF061 /* Pods */, 82 | CC6A42D3DBCBAE99EF595A9F /* Frameworks */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 607FACD11AFB9204008FA782 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 607FACD01AFB9204008FA782 /* SimplePDF_Example.app */, 90 | 607FACE51AFB9204008FA782 /* SimplePDF_Tests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | 607FACD21AFB9204008FA782 /* Example for SimplePDF */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 57E4A0381CB6A0B000EBDFEF /* Demo.png */, 99 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 100 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 101 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 102 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 103 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 104 | 607FACD31AFB9204008FA782 /* Supporting Files */, 105 | ); 106 | name = "Example for SimplePDF"; 107 | path = SimplePDF; 108 | sourceTree = ""; 109 | }; 110 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 607FACD41AFB9204008FA782 /* Info.plist */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | 607FACE81AFB9204008FA782 /* Tests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 122 | 607FACE91AFB9204008FA782 /* Supporting Files */, 123 | ); 124 | path = Tests; 125 | sourceTree = ""; 126 | }; 127 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 607FACEA1AFB9204008FA782 /* Info.plist */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | E3ACCBF4822A73427213F8A9 /* SimplePDFSwift.podspec */, 139 | 1AC8B89E3D73A8D2544B9387 /* README.md */, 140 | 3BB44BCB67803C6A1698D498 /* LICENSE */, 141 | ); 142 | name = "Podspec Metadata"; 143 | sourceTree = ""; 144 | }; 145 | 83C48FFABC027AD69AEFF061 /* Pods */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 556099EE118477E831FBE9B4 /* Pods-SimplePDF_Example.debug.xcconfig */, 149 | A247239FC55D62CD18E5A79E /* Pods-SimplePDF_Example.release.xcconfig */, 150 | B6B8A5EFE2A1705B3145BBAD /* Pods-SimplePDF_Tests.debug.xcconfig */, 151 | 3B6DD16E263E3EA17BABFB57 /* Pods-SimplePDF_Tests.release.xcconfig */, 152 | ); 153 | name = Pods; 154 | sourceTree = ""; 155 | }; 156 | CC6A42D3DBCBAE99EF595A9F /* Frameworks */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 65AB1F9B3DF65A03567EDE17 /* Pods_SimplePDF_Example.framework */, 160 | 2D27C11E0F83157C81BD71DD /* Pods_SimplePDF_Tests.framework */, 161 | ); 162 | name = Frameworks; 163 | sourceTree = ""; 164 | }; 165 | /* End PBXGroup section */ 166 | 167 | /* Begin PBXNativeTarget section */ 168 | 607FACCF1AFB9204008FA782 /* SimplePDF_Example */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SimplePDF_Example" */; 171 | buildPhases = ( 172 | 0A3536D44AD214B82749ECEE /* [CP] Check Pods Manifest.lock */, 173 | 607FACCC1AFB9204008FA782 /* Sources */, 174 | 607FACCD1AFB9204008FA782 /* Frameworks */, 175 | 607FACCE1AFB9204008FA782 /* Resources */, 176 | D0DF7144DB5FDC745788A989 /* [CP] Embed Pods Frameworks */, 177 | FD1327D06F1C03183BABF8FC /* [CP] Copy Pods Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = SimplePDF_Example; 184 | productName = SimplePDF; 185 | productReference = 607FACD01AFB9204008FA782 /* SimplePDF_Example.app */; 186 | productType = "com.apple.product-type.application"; 187 | }; 188 | 607FACE41AFB9204008FA782 /* SimplePDF_Tests */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SimplePDF_Tests" */; 191 | buildPhases = ( 192 | 499BF249C06922FEA8315837 /* [CP] Check Pods Manifest.lock */, 193 | 607FACE11AFB9204008FA782 /* Sources */, 194 | 607FACE21AFB9204008FA782 /* Frameworks */, 195 | 607FACE31AFB9204008FA782 /* Resources */, 196 | 96E1729B738B7A725CBE3BD0 /* [CP] Embed Pods Frameworks */, 197 | 9C7A30138ECC59DBB3D8BE83 /* [CP] Copy Pods Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 203 | ); 204 | name = SimplePDF_Tests; 205 | productName = Tests; 206 | productReference = 607FACE51AFB9204008FA782 /* SimplePDF_Tests.xctest */; 207 | productType = "com.apple.product-type.bundle.unit-test"; 208 | }; 209 | /* End PBXNativeTarget section */ 210 | 211 | /* Begin PBXProject section */ 212 | 607FACC81AFB9204008FA782 /* Project object */ = { 213 | isa = PBXProject; 214 | attributes = { 215 | LastSwiftUpdateCheck = 0720; 216 | LastUpgradeCheck = 0810; 217 | ORGANIZATIONNAME = CocoaPods; 218 | TargetAttributes = { 219 | 607FACCF1AFB9204008FA782 = { 220 | CreatedOnToolsVersion = 6.3.1; 221 | DevelopmentTeam = FD6GC447H6; 222 | LastSwiftMigration = 0810; 223 | }; 224 | 607FACE41AFB9204008FA782 = { 225 | CreatedOnToolsVersion = 6.3.1; 226 | LastSwiftMigration = 0810; 227 | TestTargetID = 607FACCF1AFB9204008FA782; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SimplePDF" */; 232 | compatibilityVersion = "Xcode 3.2"; 233 | developmentRegion = English; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 607FACC71AFB9204008FA782; 240 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 607FACCF1AFB9204008FA782 /* SimplePDF_Example */, 245 | 607FACE41AFB9204008FA782 /* SimplePDF_Tests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 607FACCE1AFB9204008FA782 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | 57E4A0391CB6A0B000EBDFEF /* Demo.png in Resources */, 256 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 257 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 258 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | 607FACE31AFB9204008FA782 /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXResourcesBuildPhase section */ 270 | 271 | /* Begin PBXShellScriptBuildPhase section */ 272 | 0A3536D44AD214B82749ECEE /* [CP] Check Pods Manifest.lock */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputPaths = ( 278 | ); 279 | name = "[CP] Check Pods Manifest.lock"; 280 | outputPaths = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 285 | showEnvVarsInLog = 0; 286 | }; 287 | 499BF249C06922FEA8315837 /* [CP] Check Pods Manifest.lock */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputPaths = ( 293 | ); 294 | name = "[CP] Check Pods Manifest.lock"; 295 | outputPaths = ( 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | shellPath = /bin/sh; 299 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 300 | showEnvVarsInLog = 0; 301 | }; 302 | 96E1729B738B7A725CBE3BD0 /* [CP] Embed Pods Frameworks */ = { 303 | isa = PBXShellScriptBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | ); 307 | inputPaths = ( 308 | ); 309 | name = "[CP] Embed Pods Frameworks"; 310 | outputPaths = ( 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | shellPath = /bin/sh; 314 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SimplePDF_Tests/Pods-SimplePDF_Tests-frameworks.sh\"\n"; 315 | showEnvVarsInLog = 0; 316 | }; 317 | 9C7A30138ECC59DBB3D8BE83 /* [CP] Copy Pods Resources */ = { 318 | isa = PBXShellScriptBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | ); 322 | inputPaths = ( 323 | ); 324 | name = "[CP] Copy Pods Resources"; 325 | outputPaths = ( 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | shellPath = /bin/sh; 329 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SimplePDF_Tests/Pods-SimplePDF_Tests-resources.sh\"\n"; 330 | showEnvVarsInLog = 0; 331 | }; 332 | D0DF7144DB5FDC745788A989 /* [CP] Embed Pods Frameworks */ = { 333 | isa = PBXShellScriptBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | ); 337 | inputPaths = ( 338 | ); 339 | name = "[CP] Embed Pods Frameworks"; 340 | outputPaths = ( 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | shellPath = /bin/sh; 344 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SimplePDF_Example/Pods-SimplePDF_Example-frameworks.sh\"\n"; 345 | showEnvVarsInLog = 0; 346 | }; 347 | FD1327D06F1C03183BABF8FC /* [CP] Copy Pods Resources */ = { 348 | isa = PBXShellScriptBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | ); 352 | inputPaths = ( 353 | ); 354 | name = "[CP] Copy Pods Resources"; 355 | outputPaths = ( 356 | ); 357 | runOnlyForDeploymentPostprocessing = 0; 358 | shellPath = /bin/sh; 359 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SimplePDF_Example/Pods-SimplePDF_Example-resources.sh\"\n"; 360 | showEnvVarsInLog = 0; 361 | }; 362 | /* End PBXShellScriptBuildPhase section */ 363 | 364 | /* Begin PBXSourcesBuildPhase section */ 365 | 607FACCC1AFB9204008FA782 /* Sources */ = { 366 | isa = PBXSourcesBuildPhase; 367 | buildActionMask = 2147483647; 368 | files = ( 369 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 370 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | 607FACE11AFB9204008FA782 /* Sources */ = { 375 | isa = PBXSourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 379 | ); 380 | runOnlyForDeploymentPostprocessing = 0; 381 | }; 382 | /* End PBXSourcesBuildPhase section */ 383 | 384 | /* Begin PBXTargetDependency section */ 385 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 386 | isa = PBXTargetDependency; 387 | target = 607FACCF1AFB9204008FA782 /* SimplePDF_Example */; 388 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 389 | }; 390 | /* End PBXTargetDependency section */ 391 | 392 | /* Begin PBXVariantGroup section */ 393 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 394 | isa = PBXVariantGroup; 395 | children = ( 396 | 607FACDA1AFB9204008FA782 /* Base */, 397 | ); 398 | name = Main.storyboard; 399 | sourceTree = ""; 400 | }; 401 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 402 | isa = PBXVariantGroup; 403 | children = ( 404 | 607FACDF1AFB9204008FA782 /* Base */, 405 | ); 406 | name = LaunchScreen.xib; 407 | sourceTree = ""; 408 | }; 409 | /* End PBXVariantGroup section */ 410 | 411 | /* Begin XCBuildConfiguration section */ 412 | 607FACED1AFB9204008FA782 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 423 | CLANG_WARN_EMPTY_BODY = YES; 424 | CLANG_WARN_ENUM_CONVERSION = YES; 425 | CLANG_WARN_INFINITE_RECURSION = YES; 426 | CLANG_WARN_INT_CONVERSION = YES; 427 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | ENABLE_STRICT_OBJC_MSGSEND = YES; 435 | ENABLE_TESTABILITY = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_DYNAMIC_NO_PIC = NO; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_OPTIMIZATION_LEVEL = 0; 440 | GCC_PREPROCESSOR_DEFINITIONS = ( 441 | "DEBUG=1", 442 | "$(inherited)", 443 | ); 444 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 452 | MTL_ENABLE_DEBUG_INFO = YES; 453 | ONLY_ACTIVE_ARCH = YES; 454 | SDKROOT = iphoneos; 455 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 456 | }; 457 | name = Debug; 458 | }; 459 | 607FACEE1AFB9204008FA782 /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 464 | CLANG_CXX_LIBRARY = "libc++"; 465 | CLANG_ENABLE_MODULES = YES; 466 | CLANG_ENABLE_OBJC_ARC = YES; 467 | CLANG_WARN_BOOL_CONVERSION = YES; 468 | CLANG_WARN_CONSTANT_CONVERSION = YES; 469 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 470 | CLANG_WARN_EMPTY_BODY = YES; 471 | CLANG_WARN_ENUM_CONVERSION = YES; 472 | CLANG_WARN_INFINITE_RECURSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 476 | CLANG_WARN_UNREACHABLE_CODE = YES; 477 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | COPY_PHASE_STRIP = NO; 480 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 481 | ENABLE_NS_ASSERTIONS = NO; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | GCC_C_LANGUAGE_STANDARD = gnu99; 484 | GCC_NO_COMMON_BLOCKS = YES; 485 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 486 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 487 | GCC_WARN_UNDECLARED_SELECTOR = YES; 488 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 489 | GCC_WARN_UNUSED_FUNCTION = YES; 490 | GCC_WARN_UNUSED_VARIABLE = YES; 491 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 492 | MTL_ENABLE_DEBUG_INFO = NO; 493 | SDKROOT = iphoneos; 494 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 495 | VALIDATE_PRODUCT = YES; 496 | }; 497 | name = Release; 498 | }; 499 | 607FACF01AFB9204008FA782 /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | baseConfigurationReference = 556099EE118477E831FBE9B4 /* Pods-SimplePDF_Example.debug.xcconfig */; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | DEVELOPMENT_TEAM = FD6GC447H6; 505 | INFOPLIST_FILE = SimplePDF/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 507 | MODULE_NAME = ExampleApp; 508 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SWIFT_VERSION = 3.0; 511 | }; 512 | name = Debug; 513 | }; 514 | 607FACF11AFB9204008FA782 /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | baseConfigurationReference = A247239FC55D62CD18E5A79E /* Pods-SimplePDF_Example.release.xcconfig */; 517 | buildSettings = { 518 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 519 | DEVELOPMENT_TEAM = FD6GC447H6; 520 | INFOPLIST_FILE = SimplePDF/Info.plist; 521 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 522 | MODULE_NAME = ExampleApp; 523 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 524 | PRODUCT_NAME = "$(TARGET_NAME)"; 525 | SWIFT_VERSION = 3.0; 526 | }; 527 | name = Release; 528 | }; 529 | 607FACF31AFB9204008FA782 /* Debug */ = { 530 | isa = XCBuildConfiguration; 531 | baseConfigurationReference = B6B8A5EFE2A1705B3145BBAD /* Pods-SimplePDF_Tests.debug.xcconfig */; 532 | buildSettings = { 533 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 534 | GCC_PREPROCESSOR_DEFINITIONS = ( 535 | "DEBUG=1", 536 | "$(inherited)", 537 | ); 538 | INFOPLIST_FILE = Tests/Info.plist; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 540 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | SWIFT_VERSION = 3.0; 543 | }; 544 | name = Debug; 545 | }; 546 | 607FACF41AFB9204008FA782 /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | baseConfigurationReference = 3B6DD16E263E3EA17BABFB57 /* Pods-SimplePDF_Tests.release.xcconfig */; 549 | buildSettings = { 550 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 551 | INFOPLIST_FILE = Tests/Info.plist; 552 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 553 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | SWIFT_VERSION = 3.0; 556 | }; 557 | name = Release; 558 | }; 559 | /* End XCBuildConfiguration section */ 560 | 561 | /* Begin XCConfigurationList section */ 562 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SimplePDF" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 607FACED1AFB9204008FA782 /* Debug */, 566 | 607FACEE1AFB9204008FA782 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SimplePDF_Example" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 607FACF01AFB9204008FA782 /* Debug */, 575 | 607FACF11AFB9204008FA782 /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SimplePDF_Tests" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 607FACF31AFB9204008FA782 /* Debug */, 584 | 607FACF41AFB9204008FA782 /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | /* End XCConfigurationList section */ 590 | }; 591 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 592 | } 593 | -------------------------------------------------------------------------------- /Pod/Classes/SimplePDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePDF.swift 3 | // 4 | // Created by Muhammad Ishaq on 22/03/2015. 5 | // 6 | 7 | import Foundation 8 | import UIKit 9 | import ImageIO 10 | import CoreText 11 | //import XCGLogger 12 | 13 | /** 14 | * Generate Simple Documents with Images. TOC gets generated and put at (roughly) specified page index 15 | */ 16 | open class SimplePDF { 17 | 18 | open static let defaultBackgroundBoxColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) 19 | open static let defaultSpacing:CGFloat = 8 20 | open static let pageNumberPlaceholder = "{{PAGE_NUMBER}}" 21 | open static let pagesCountPlaceholder = "{{PAGES_COUNT}}" 22 | 23 | // MARK: - Document Structure 24 | fileprivate class DocumentStructure { 25 | // MARK: - FunctionCall (sort of a "Command" pattern) 26 | fileprivate enum FunctionCall : CustomStringConvertible { 27 | case addH1(string: String, backgroundBoxColor: UIColor?) 28 | case addH2(string: String, backgroundBoxColor: UIColor?) 29 | case addH3(string: String, backgroundBoxColor: UIColor?) 30 | case addH4(string: String, backgroundBoxColor: UIColor?) 31 | case addH5(string: String, backgroundBoxColor: UIColor?) 32 | case addH6(string: String, backgroundBoxColor: UIColor?) 33 | case addBodyText(string: String, backgroundBoxColor: UIColor?) 34 | case startNewPage 35 | case addImages(imagePaths:[String], imageCaptions: [String], imagesPerRow:Int, spacing:CGFloat, padding:CGFloat) 36 | case addImagesRow(imagePaths: [String], imageCaptions: [NSAttributedString], columnWidths: [CGFloat], 37 | spacing: CGFloat, padding: CGFloat, captionBackgroundColor: UIColor?, imageBackgroundColor: UIColor?) 38 | case addAttributedStringsToColumns(columnWidths: [CGFloat], strings: [NSAttributedString], horizontalPadding: CGFloat, allowSplitting: Bool, boxStyle: BoxStyle?, withVerticalDividerLine : Bool) 39 | case addView(view: UIView) 40 | case addVerticalSpace(space: CGFloat) 41 | 42 | var description: String { 43 | get { 44 | switch(self) { 45 | case .addH1(let string, _ /*let backgroundBoxColor*/): 46 | return "addH1 (\(string))" 47 | case .addH2(let string, _ /*let backgroundBoxColor*/): 48 | return "addH2 (\(string))" 49 | case .addH3(let string, _ /*let backgroundBoxColor*/): 50 | return "addH3 (\(string))" 51 | case .addH4(let string, _ /*let backgroundBoxColor*/): 52 | return "addH4 (\(string))" 53 | case .addH5(let string, _ /*let backgroundBoxColor*/): 54 | return "addH5 (\(string))" 55 | case .addH6(let string, _ /*let backgroundBoxColor*/): 56 | return "addH6 (\(string))" 57 | case .addBodyText(let string, _ /*let backgroundBoxColor*/): 58 | return "addBodyText (\(string.substring(to: string.characters.index(string.startIndex, offsetBy: 25))))" 59 | case .startNewPage: 60 | return "startNewPage" 61 | case .addImages/*(let imagePaths, let imageCaptions, let imagesPerRow, let spacing, let padding)*/: 62 | return "addImages" 63 | case .addImagesRow/*(let imagePaths, let imageCaptions, let columnWidths, let spacing, let padding, let captionBackgroundColor, let imageBackgroundColor)*/: 64 | return "addImagesRow" 65 | case .addAttributedStringsToColumns/*(let columnWidths, let strings, let horizontalPadding, let allowSplitting, let backgroundColor)*/: 66 | return "addAttributedStringsToColumns" 67 | case .addView/*(let view)*/: 68 | return "addView" 69 | case .addVerticalSpace(let space): 70 | return "addVerticalSpace (\(space))" 71 | } 72 | 73 | } 74 | } 75 | 76 | var contentLevel : Int { 77 | get { 78 | switch(self) { 79 | case .addH1: 80 | return 1 81 | case .addH2: 82 | return 2 83 | case .addH3: 84 | return 3 85 | case .addH4: 86 | return 3 87 | case .addH5: 88 | return 5 89 | case .addH6: 90 | return 6 91 | default: 92 | return 7 93 | } 94 | } 95 | } 96 | 97 | func execute(_ pdf: PDFWriter, calculationOnly: Bool = true) -> NSRange { 98 | var pageRange = NSMakeRange(0, 0) 99 | switch(self) { 100 | case .addH1(let string, let backgroundBoxColor): 101 | pageRange = pdf.addHeadline(string, style: .h1, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 102 | case .addH2(let string, let backgroundBoxColor): 103 | pageRange = pdf.addHeadline(string, style: .h2, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 104 | case .addH3(let string, let backgroundBoxColor): 105 | pageRange = pdf.addHeadline(string, style: .h3, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 106 | case .addH4(let string, let backgroundBoxColor): 107 | pageRange = pdf.addHeadline(string, style: .h4, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 108 | case .addH5(let string, let backgroundBoxColor): 109 | pageRange = pdf.addHeadline(string, style: .h5, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 110 | case .addH6(let string, let backgroundBoxColor): 111 | pageRange = pdf.addHeadline(string, style: .h6, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 112 | case .addBodyText(let string, let backgroundBoxColor): 113 | pageRange = pdf.addBodyText(string, backgroundBoxColor: backgroundBoxColor, calculationOnly: calculationOnly) 114 | case .startNewPage: 115 | pageRange = pdf.startNewPage(calculationOnly) 116 | case .addImages(let imagePaths, let imageCaptions, let imagesPerRow, let spacing, let padding): 117 | pageRange = pdf.addImages(imagePaths: imagePaths, imageCaptions: imageCaptions, imagesPerRow: imagesPerRow, spacing: spacing, padding: padding, calculationOnly: calculationOnly) 118 | case .addImagesRow(let imagePaths, let imageCaptions, let columnWidths, let spacing, let padding, let captionBackgroundColor, let imageBackgroundColor): 119 | pageRange = pdf.addImagesRow(imagePaths, imageCaptions: imageCaptions, columnWidths: columnWidths, spacing: spacing, padding: padding, captionBackgroundColor: captionBackgroundColor, imageBackgroundColor: imageBackgroundColor, calculationOnly: calculationOnly) 120 | case .addAttributedStringsToColumns(let columnWidths, let strings, let horizontalPadding, let allowSplitting, let boxStyle, let withVerticalDividerLine): 121 | pageRange = pdf.addAttributedStringsToColumns(columnWidths, strings: strings, horizontalPadding: horizontalPadding, allowSplitting: allowSplitting, boxStyle: boxStyle, calculationOnly: calculationOnly, withVerticalDividerLine: withVerticalDividerLine) 122 | case .addView(let view): 123 | pageRange = pdf.addView(view, calculationOnly: calculationOnly) 124 | case .addVerticalSpace(let space): 125 | pageRange = pdf.addVerticalSpace(space, calculationOnly: calculationOnly) 126 | } 127 | 128 | return pageRange 129 | } 130 | 131 | func getTableOfContentsInfo() -> (TextStyle, String?) { 132 | switch(self) { 133 | case .addH1(let string, _ /*let backgroundBoxColor*/): 134 | return (.h1, string) 135 | case .addH2(let string, _ /*let backgroundBoxColor*/): 136 | return (.h2, string) 137 | case .addH3(let string, _ /*let backgroundBoxColor*/): 138 | return (.h3, string) 139 | case .addH4(let string, _ /*let backgroundBoxColor*/): 140 | return (.h4, string) 141 | case .addH5(let string, _ /*let backgroundBoxColor*/): 142 | return (.h5, string) 143 | case .addH6(let string, _ /*let backgroundBoxColor*/): 144 | return (.h6, string) 145 | default: 146 | return (.bodyText, nil) 147 | } 148 | } 149 | 150 | } 151 | 152 | // MARK: - Document Node 153 | fileprivate class DocumentElement { 154 | var functionCall: FunctionCall 155 | var pageRange: NSRange 156 | 157 | init(functionCall: FunctionCall, pageRange: NSRange) { 158 | self.functionCall = functionCall 159 | self.pageRange = pageRange 160 | } 161 | 162 | func executeFunctionCall(_ pdf: PDFWriter, calculationOnly: Bool = true) -> NSRange { 163 | self.pageRange = self.functionCall.execute(pdf, calculationOnly: calculationOnly) 164 | return self.pageRange 165 | } 166 | } 167 | 168 | // MARK: - TableOfContentsNode 169 | fileprivate class TableOfContentsElement { 170 | var attrString: NSAttributedString 171 | var pageIndex: Int 172 | 173 | init(attrString: NSAttributedString, pageIndex: Int) { 174 | self.attrString = attrString 175 | self.pageIndex = pageIndex 176 | } 177 | } 178 | 179 | fileprivate var documentElements = [DocumentElement]() 180 | fileprivate var tableOfContents = Array() 181 | fileprivate var tableOfContentsPagesRange = NSMakeRange(0, 0) 182 | 183 | var tableOfContentsOnPage = 1 184 | var biggestHeadingToIncludeInTOC = TextStyle.h1 185 | var smallestHeadingToIncludeInTOC = TextStyle.h6 186 | 187 | // NOTE: this page only fills in the page numbers as if TOC would never be inserted into the document 188 | // actual page numbers are calculated within the drawTableOfContentsCall 189 | fileprivate func generateTableOfContents() -> Array { 190 | var tableOfContents = Array() 191 | 192 | var pageIndex = -1 193 | for docNode in documentElements { 194 | pageIndex += docNode.pageRange.location 195 | 196 | let (textStyle, label) = docNode.functionCall.getTableOfContentsInfo() 197 | 198 | if let heading = label { 199 | if(textStyle.rawValue >= biggestHeadingToIncludeInTOC.rawValue && textStyle.rawValue <= smallestHeadingToIncludeInTOC.rawValue) { 200 | // TODO: create a properly formatted string 201 | let tocNode = TableOfContentsElement(attrString: NSAttributedString(string: heading), pageIndex: pageIndex) 202 | tableOfContents.append(tocNode) 203 | //XCGLogger.debug("TOC: \(pageIndex) \(heading)") 204 | } 205 | } 206 | 207 | pageIndex += docNode.pageRange.length 208 | } 209 | return tableOfContents 210 | } 211 | /* 212 | var pagesCount: Int { 213 | get { 214 | if(document.count == 0) { 215 | XCGLogger.warning("document doesn't have any elements, pagesCount would not be accurate") 216 | } 217 | if((tableOfContentsPagesRange.location + tableOfContentsPagesRange.length) == 0) { 218 | XCGLogger.warning("table of contents not laid out, pagesCount would not be accurate") 219 | } 220 | var pagesCount = 0 221 | for (var i = 0; i < document.count; i++) { 222 | let docNode = document[i] 223 | //XCGLogger.debug("\(i) \(docNode.functionCall) \(StringFromRange(docNode.pageRange))") 224 | pagesCount += (docNode.pageRange.location + docNode.pageRange.length) 225 | } 226 | 227 | pagesCount += (tableOfContentsPagesRange.location + tableOfContentsPagesRange.length) 228 | 229 | return pagesCount 230 | } 231 | }*/ 232 | } 233 | 234 | // MARK: - Text Style 235 | public enum TextStyle: Int { 236 | case h1 = 0 237 | case h2 = 1 238 | case h3 = 2 239 | case h4 = 3 240 | case h5 = 4 241 | case h6 = 5 242 | case bodyText = 6 243 | } 244 | 245 | public struct BorderEdge : OptionSet{ 246 | public let rawValue : Int 247 | public init(rawValue:Int){ self.rawValue = rawValue} 248 | public static let Top = BorderEdge(rawValue:1) 249 | public static let Left = BorderEdge(rawValue:2) 250 | public static let Right = BorderEdge(rawValue:4) 251 | public static let Bottom = BorderEdge(rawValue:8) 252 | } 253 | 254 | public struct BorderStyle { 255 | var borderEdges : BorderEdge 256 | var color : UIColor? 257 | var width : CGFloat 258 | public init(borderEdges : BorderEdge, color : UIColor? = nil, width : CGFloat = 0.5) { 259 | self.borderEdges = borderEdges 260 | self.color = color 261 | self.width = width 262 | } 263 | } 264 | 265 | public struct BoxStyle { 266 | var boders : [BorderStyle]? 267 | var backgroundColor : UIColor? 268 | } 269 | 270 | // MARK: - Text Formatter 271 | open class DefaultTextFormatter { 272 | public init() { } 273 | 274 | open func attributedStringForStyle(_ string: String, style: TextStyle) -> NSAttributedString { 275 | let attrString = NSMutableAttributedString(string: string) 276 | 277 | let paragraphStyle = NSMutableParagraphStyle() 278 | switch(style) { 279 | case .h1: 280 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 24), range: NSMakeRange(0, attrString.length)) 281 | paragraphStyle.alignment = .center 282 | case .h2: 283 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 20), range: NSMakeRange(0, attrString.length)) 284 | paragraphStyle.alignment = .center 285 | case .h3: 286 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 16), range: NSMakeRange(0, attrString.length)) 287 | paragraphStyle.alignment = .center 288 | case .h4: 289 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 14), range: NSMakeRange(0, attrString.length)) 290 | case .h5: 291 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 12), range: NSMakeRange(0, attrString.length)) 292 | case .h6: 293 | attrString.addAttribute(NSFontAttributeName, value:UIFont.boldSystemFont(ofSize: 10), range: NSMakeRange(0, attrString.length)) 294 | case .bodyText: 295 | attrString.addAttribute(NSFontAttributeName, value:UIFont.systemFont(ofSize: 10), range: NSMakeRange(0, attrString.length)) 296 | } 297 | 298 | attrString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, attrString.length)) 299 | return attrString 300 | } 301 | 302 | open func backgroundColorForStyle(_ style: TextStyle) -> UIColor? { 303 | switch style { 304 | case .h1, .h2, .h3: 305 | return defaultBackgroundBoxColor 306 | default: 307 | return nil 308 | } 309 | } 310 | 311 | open func bordersForStyle(_ style: TextStyle) ->[BorderStyle]? { 312 | return nil 313 | } 314 | 315 | open func boxStyleForTextStyle(_ style: TextStyle) -> BoxStyle { 316 | return BoxStyle(boders: self.bordersForStyle(style), backgroundColor: self.backgroundColorForStyle(style)) 317 | } 318 | 319 | } 320 | 321 | // MARK: - PDFWriter 322 | fileprivate class PDFWriter { 323 | fileprivate var textFormatter: DefaultTextFormatter 324 | 325 | fileprivate var pageSize: PageSize 326 | fileprivate var pageOrientation: PageOrientation 327 | fileprivate var leftMargin:CGFloat 328 | fileprivate var rightMargin: CGFloat 329 | fileprivate var topMargin: CGFloat 330 | fileprivate var bottomMargin: CGFloat 331 | 332 | fileprivate var currentPage = -1 333 | fileprivate var pagesCount = 0 334 | fileprivate var currentLocation = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude) 335 | fileprivate var availablePageRect = CGRect.zero 336 | 337 | var headerFooterTexts: Array 338 | var headerFooterImages: Array 339 | 340 | init(textFormatter: DefaultTextFormatter, pageSize: PageSize, pageOrientation: PageOrientation, leftMargin: CGFloat, rightMargin: CGFloat, 341 | topMargin: CGFloat, bottomMargin: CGFloat, pagesCount: Int, headerFooterTexts: Array, 342 | headerFooterImages: Array) { 343 | self.textFormatter = textFormatter 344 | self.pageSize = pageSize 345 | self.pageOrientation = pageOrientation 346 | self.leftMargin = leftMargin 347 | self.rightMargin = rightMargin 348 | self.topMargin = topMargin 349 | self.bottomMargin = bottomMargin 350 | 351 | self.pagesCount = pagesCount 352 | self.headerFooterImages = headerFooterImages 353 | self.headerFooterTexts = headerFooterTexts 354 | 355 | let bounds = getPageBounds() 356 | let origin = CGPoint(x: bounds.origin.x + leftMargin, y: bounds.origin.y + topMargin) 357 | let size = CGSize(width: bounds.size.width - (leftMargin + rightMargin), 358 | height: bounds.size.height - (topMargin + bottomMargin)) 359 | self.availablePageRect = CGRect(origin: origin, size: size) 360 | } 361 | 362 | var availablePageSize: CGSize { 363 | get { return availablePageRect.size } 364 | } 365 | 366 | fileprivate func openPDF(_ path: String, title: String?, author: String?) -> NSError? { 367 | let pageRect = getPageBounds() 368 | 369 | var documentInfo = [kCGPDFContextCreator as String: "\(SimplePDFUtilities.getApplicationName()) \(SimplePDFUtilities.getApplicationVersion())"] 370 | if let a = author { 371 | documentInfo[kCGPDFContextAuthor as String] = a 372 | } 373 | if let t = title { 374 | documentInfo[kCGPDFContextTitle as String] = t 375 | } 376 | 377 | UIGraphicsBeginPDFContextToFile(path, pageRect, documentInfo) 378 | 379 | //currentLocation.y = CGFloat.max 380 | //startNewPage() 381 | return nil 382 | } 383 | 384 | 385 | fileprivate func closePDF() { 386 | UIGraphicsEndPDFContext() 387 | } 388 | 389 | fileprivate func startNewPage(_ calculationOnly: Bool = false) -> NSRange { 390 | if(calculationOnly == false) { 391 | UIGraphicsBeginPDFPage() 392 | } 393 | currentPage += 1 394 | currentLocation = CGPoint.zero 395 | if(calculationOnly == false) { 396 | addPageHeaderFooter() 397 | } 398 | 399 | return NSMakeRange(1, 0) 400 | } 401 | 402 | // MARK: Headers and Footers 403 | fileprivate func addPageHeaderFooter() { 404 | /*if(pagesCount == 0) { 405 | XCGLogger.warning("pages count not assigned, if it's printed in a header/footer, it would be wrong.") 406 | }*/ 407 | // draw top line 408 | drawLine(CGPoint(x: availablePageRect.origin.x, y: availablePageRect.origin.y-10), 409 | p2: CGPoint(x: availablePageRect.origin.x + availablePageRect.size.width, y: availablePageRect.origin.y - 10)) 410 | // draw bottom line 411 | drawLine(CGPoint(x: availablePageRect.origin.x, y: availablePageRect.origin.y + availablePageRect.size.height + 1), 412 | p2: CGPoint(x: availablePageRect.origin.x + availablePageRect.size.width, y: availablePageRect.origin.y + availablePageRect.size.height + 1)) 413 | 414 | for i in 0 ..< self.headerFooterTexts.count { 415 | var text = self.headerFooterTexts[i] 416 | let textString = NSMutableAttributedString(attributedString: text.attributedString) 417 | textString.mutableString.replaceOccurrences(of: pageNumberPlaceholder, with: "\(currentPage + 1)", options: [], range: NSMakeRange(0, textString.length)) 418 | textString.mutableString.replaceOccurrences(of: pagesCountPlaceholder, with: "\(pagesCount)", options: [], range: NSMakeRange(0, textString.length)) 419 | text.attributedString = textString 420 | if NSLocationInRange(currentPage, text.pageRange) { 421 | switch(text.type) { 422 | case .header: 423 | addHeaderText(text) 424 | case .footer: 425 | addFooterText(text) 426 | } 427 | } 428 | } 429 | 430 | for i in 0 ..< self.headerFooterImages.count { 431 | let image = self.headerFooterImages[i] 432 | if(image.imagePath.isEmpty && image.image == nil) { 433 | print("ERROR: image path is empty and image is null, skipping") 434 | continue 435 | } 436 | if NSLocationInRange(currentPage, image.pageRange) { 437 | switch(image.type) { 438 | case .header: 439 | addHeaderImage(image) 440 | case .footer: 441 | addFooterImage(image) 442 | } 443 | } 444 | } 445 | } 446 | 447 | fileprivate func addHeaderText(_ header: HeaderFooterText) { 448 | let availableHeight = topMargin - 11 449 | let framesetter = CTFramesetterCreateWithAttributedString(header.attributedString) 450 | var suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, CGSize(width: availablePageRect.width, height: CGFloat.greatestFiniteMagnitude), nil) 451 | if(suggestedSize.height > availableHeight) { 452 | suggestedSize.height = availableHeight 453 | } 454 | 455 | let textRect = CGRect(x: availablePageRect.origin.x, y: availableHeight - suggestedSize.height, width: availablePageRect.width, height: suggestedSize.height) 456 | 457 | drawHeaderFooterText(framesetter, textRect: textRect) 458 | } 459 | 460 | fileprivate func addFooterText(_ footer: HeaderFooterText) { 461 | let availableHeight = bottomMargin - 2 462 | let framesetter = CTFramesetterCreateWithAttributedString(footer.attributedString) 463 | var suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, CGSize(width: availablePageRect.width, height: CGFloat.greatestFiniteMagnitude), nil) 464 | if(suggestedSize.height > availableHeight) { 465 | suggestedSize.height = availableHeight 466 | } 467 | 468 | let textRect = CGRect(x: availablePageRect.origin.x, y: availablePageRect.origin.y + availablePageRect.size.height + 2, width: availablePageRect.width, height: suggestedSize.height) 469 | 470 | drawHeaderFooterText(framesetter, textRect: textRect) 471 | 472 | } 473 | 474 | fileprivate func drawHeaderFooterText(_ framesetter: CTFramesetter, textRect textRectIn: CGRect) { 475 | var textRect = textRectIn 476 | textRect = convertRectToCoreTextCoordinates(textRect) 477 | 478 | guard let context = UIGraphicsGetCurrentContext() else { 479 | return 480 | } 481 | 482 | let bounds = getPageBounds() 483 | context.textMatrix = CGAffineTransform.identity 484 | context.translateBy(x: 0, y: bounds.size.height) 485 | context.scaleBy(x: 1.0, y: -1.0) 486 | 487 | 488 | let textPath = CGMutablePath() 489 | textPath.addRect(textRect) 490 | //CGPathAddRect(textPath, nil, textRect) 491 | let frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), textPath, nil) 492 | 493 | CTFrameDraw(frameRef, context) 494 | 495 | // flip it back 496 | context.scaleBy(x: 1.0, y: -1.0) 497 | context.translateBy(x: 0, y: -bounds.size.height) 498 | } 499 | 500 | fileprivate func addHeaderImage(_ header: HeaderFooterImage) { 501 | let availableHeight = topMargin - 11 502 | var imageHeight = header.imageHeight 503 | if(imageHeight > availableHeight) { 504 | imageHeight = availableHeight 505 | } 506 | var image = header.image 507 | if(image == nil) { 508 | image = UIImage(contentsOfFile: header.imagePath) 509 | if(image == nil) { 510 | print("ERROR: Unable to read image: \(header.imagePath)") 511 | return 512 | } 513 | } 514 | 515 | let y = availableHeight - imageHeight 516 | drawHeaderFooterImage(header.alignment, image: image!, imageHeight: imageHeight, y: y) 517 | } 518 | 519 | fileprivate func addFooterImage(_ footer: HeaderFooterImage) { 520 | let availableHeight = bottomMargin - 2 521 | var imageHeight = footer.imageHeight 522 | if(imageHeight > availableHeight) { 523 | imageHeight = availableHeight 524 | } 525 | var image = footer.image 526 | if(image == nil) { 527 | image = UIImage(contentsOfFile: footer.imagePath) 528 | if(image == nil) { 529 | print("ERROR: Unable to read image: \(footer.imagePath)") 530 | return 531 | } 532 | } 533 | let y = availablePageRect.origin.y + availablePageRect.size.height + 2 534 | drawHeaderFooterImage(footer.alignment, image: image!, imageHeight: imageHeight, y: y) 535 | } 536 | 537 | fileprivate func drawHeaderFooterImage(_ alignment: NSTextAlignment, image: UIImage, imageHeight: CGFloat, y: CGFloat) { 538 | var x:CGFloat = 0 539 | let imageWidth = aspectFitWidthForHeight(image.size, height: imageHeight) 540 | switch(alignment) { 541 | case .left: 542 | x = availablePageRect.origin.x 543 | break; 544 | case .center: 545 | x = availablePageRect.origin.x + ((availablePageRect.size.width - imageWidth) / 2) 546 | break; 547 | default: // align right 548 | x = (availablePageRect.origin.x + availablePageRect.size.width) - imageWidth 549 | break; 550 | } 551 | 552 | let imageRect = CGRect(x: x, y: y, width: imageWidth, height: imageHeight) 553 | image.draw(in: imageRect) 554 | } 555 | 556 | // MARK: - Document elements 557 | 558 | fileprivate func addHeadline(_ string: String, style: TextStyle, backgroundBoxColor: UIColor? = nil, calculationOnly: Bool = false) -> NSRange { 559 | 560 | guard [TextStyle.h1, TextStyle.h2, TextStyle.h3, TextStyle.h4, TextStyle.h5, TextStyle.h6].contains(style) else { 561 | fatalError("style must be a headline, but was: \(style)") 562 | } 563 | 564 | let attrString = textFormatter.attributedStringForStyle(string, style: style) 565 | 566 | var boxStyle = textFormatter.boxStyleForTextStyle(style) 567 | if(backgroundBoxColor != nil) { 568 | boxStyle.backgroundColor = backgroundBoxColor 569 | } 570 | return addAttributedString(attrString, allowSplitting: false, boxStyle: boxStyle, calculationOnly: calculationOnly) 571 | } 572 | 573 | fileprivate func addBodyText(_ string: String, backgroundBoxColor: UIColor? = nil, calculationOnly: Bool = false) -> NSRange { 574 | let attrString = textFormatter.attributedStringForStyle(string, style: .bodyText) 575 | var boxStyle = textFormatter.boxStyleForTextStyle(.bodyText) 576 | if(backgroundBoxColor != nil) { 577 | boxStyle.backgroundColor = backgroundBoxColor 578 | } 579 | return addAttributedString(attrString, allowSplitting: true, boxStyle: boxStyle, calculationOnly: calculationOnly) 580 | } 581 | 582 | fileprivate func addImages(imagePaths imagePathsIn:[String], imageCaptions: [String], imagesPerRow:Int = 3, spacing:CGFloat = 2, padding:CGFloat = 5, calculationOnly: Bool = false) -> NSRange { 583 | var imagePaths = imagePathsIn 584 | assert(imagePaths.count == imageCaptions.count, "image paths and image captions don't have same number of elements") 585 | 586 | var funcCallRange = NSMakeRange(0, 0) 587 | 588 | var columnWidths = Array() 589 | let singleColumnWidth = availablePageRect.size.width / CGFloat(imagesPerRow) 590 | for _ in 0 ..< imagesPerRow { 591 | columnWidths.append(singleColumnWidth) 592 | } 593 | 594 | var attributedImageCaptions = Array() 595 | for i in 0 ..< imageCaptions.count { 596 | let mutableCaption = NSMutableAttributedString(attributedString: textFormatter.attributedStringForStyle(imageCaptions[i], style: .h6)) 597 | /* this doesn't work since captions are drawn using CTLine 598 | let paragraphStyle = NSMutableParagraphStyle() 599 | paragraphStyle.alignment = .Center 600 | mutableCaption.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, mutableCaption.length)) */ 601 | attributedImageCaptions.append(mutableCaption) 602 | } 603 | 604 | 605 | var rowIndex = 0 606 | repeat { 607 | var itemsToGet = imagesPerRow 608 | if(imagePaths.count < itemsToGet){ 609 | itemsToGet = imagePaths.count 610 | } 611 | let rowImages = Array(imagePaths[0.. 0) 627 | return funcCallRange 628 | } 629 | 630 | fileprivate func addImagesRow(_ imagePaths: [String], imageCaptions: [NSAttributedString], columnWidths: [CGFloat], 631 | spacing: CGFloat = 2, padding: CGFloat = 5, captionBackgroundColor: UIColor? = nil, 632 | imageBackgroundColor: UIColor? = nil, calculationOnly: Bool = false) -> NSRange { 633 | assert(imagePaths.count == imageCaptions.count && imageCaptions.count == columnWidths.count, 634 | "image paths, image captions and column widths don't have same number of elements") 635 | 636 | var funcCallRange = NSMakeRange(0, 0) 637 | 638 | var imageProperties = Array() 639 | for i in 0 ..< imagePaths.count { 640 | if(imagePaths[i].isEmpty) { 641 | imageProperties.append(NSDictionary()) 642 | continue 643 | } 644 | let thisImageProperties = SimplePDFUtilities.getImageProperties(imagePaths[i]) 645 | imageProperties.append(thisImageProperties) 646 | } 647 | 648 | var maxLineHeight:CGFloat = 0 649 | for i in 0 ..< imageCaptions.count { 650 | let thisCaption = imageCaptions[i] 651 | if(thisCaption.length == 0) { 652 | continue 653 | } 654 | let line = CTLineCreateWithAttributedString(thisCaption) 655 | let lineBounds = CTLineGetBoundsWithOptions(line, CTLineBoundsOptions.useGlyphPathBounds) 656 | 657 | if(lineBounds.size.height > maxLineHeight) { 658 | maxLineHeight = lineBounds.size.height 659 | } 660 | } 661 | 662 | // start a new page if needed 663 | for i in 0 ..< imagePaths.count { 664 | let thisWidth = columnWidths[i] - (2 * padding) 665 | let availableSpace = CGSize(width: CGFloat.greatestFiniteMagnitude, height: availablePageRect.size.height - currentLocation.y) 666 | 667 | let thisProperties = imageProperties[i] 668 | if thisProperties.allKeys.count == 0 { 669 | continue 670 | } 671 | let imageWidth = thisProperties[kCGImagePropertyPixelWidth as String] as! CGFloat 672 | let imageHeight = thisProperties[kCGImagePropertyPixelHeight as String] as! CGFloat 673 | 674 | let imageSize = CGSize(width: imageWidth, height:imageHeight) 675 | 676 | let fitHeight = self.aspectFitHeightForWidth(imageSize, width: thisWidth) 677 | if(fitHeight + maxLineHeight > availableSpace.height) { 678 | funcCallRange.location = 1 679 | startNewPage(calculationOnly) 680 | break 681 | } 682 | } 683 | 684 | currentLocation.y += padding 685 | var maxHeightRendered: CGFloat = 0 686 | var loc = currentLocation 687 | for i in 0 ..< imagePaths.count { 688 | // render the label 689 | var currentY = loc.y 690 | let thisCaption = imageCaptions[i] 691 | let thisWidth = columnWidths[i] 692 | var availableSpace = CGSize(width: thisWidth - (2 * padding), height: availablePageRect.size.height - loc.y) 693 | 694 | if(thisCaption.length != 0) { 695 | let line = CTLineCreateWithAttributedString(thisCaption) 696 | let truncationToken = CTLineCreateWithAttributedString(NSAttributedString(string:"…")) 697 | let truncatedLine = CTLineCreateTruncatedLine(line, Double(availableSpace.width), CTLineTruncationType.end, truncationToken) 698 | 699 | if(calculationOnly == false) { 700 | if(captionBackgroundColor != nil) { 701 | let originalTextRect = CGRect(x: availablePageRect.origin.x + loc.x + padding, y: availablePageRect.origin.y + currentY, 702 | width: thisWidth - (2 * padding), height: maxLineHeight + spacing) 703 | drawRect(originalTextRect, fillColor: captionBackgroundColor) 704 | } 705 | 706 | } 707 | let originalPoint = CGPoint(x: availablePageRect.origin.x + loc.x + padding, y: availablePageRect.origin.y + currentY) 708 | var textPoint = convertPointToCoreTextCoordinates(originalPoint) 709 | 710 | // since we need to provide the base line coordinates to CoreText, we should subtract the maxLineHeight 711 | textPoint.y -= maxLineHeight 712 | 713 | if(calculationOnly == false) { 714 | // flip context 715 | guard let context = UIGraphicsGetCurrentContext() else { 716 | continue 717 | } 718 | 719 | let bounds = UIGraphicsGetPDFContextBounds() 720 | context.textMatrix = CGAffineTransform.identity 721 | context.translateBy(x: bounds.origin.x, y: bounds.size.height) 722 | context.scaleBy(x: 1.0, y: -1.0) 723 | context.textPosition = textPoint 724 | 725 | //CGContextSetTextPosition(context, textPoint.x, textPoint.y) 726 | CTLineDraw(truncatedLine!, context) 727 | 728 | // flip it back 729 | context.scaleBy(x: 1.0, y: -1.0) 730 | context.translateBy(x: -bounds.origin.x, y: -bounds.size.height) 731 | } 732 | } 733 | 734 | currentY += (maxLineHeight + spacing) 735 | availableSpace.height -= (maxLineHeight + spacing) 736 | 737 | // render the image 738 | let thisProperties = imageProperties[i] 739 | 740 | if(thisProperties.allKeys.count == 0) { 741 | // advance to next column 742 | loc.x += thisWidth 743 | continue 744 | } 745 | 746 | let imageWidth = thisProperties[kCGImagePropertyPixelWidth as String] as! CGFloat 747 | let imageHeight = thisProperties[kCGImagePropertyPixelHeight as String] as! CGFloat 748 | 749 | let originalImageSize = CGSize(width: imageWidth, height:imageHeight) 750 | 751 | let fitHeight = self.aspectFitHeightForWidth(originalImageSize, width: availableSpace.width) 752 | var imageSizeToRender = CGSize(width: availableSpace.width, height: fitHeight) 753 | if(fitHeight > availableSpace.height) { 754 | let fitWidth = self.aspectFitWidthForHeight(originalImageSize, height: availableSpace.height) 755 | imageSizeToRender = CGSize(width: fitWidth, height: availableSpace.height) 756 | } 757 | 758 | if(calculationOnly == false) { 759 | if(imageBackgroundColor != nil) { 760 | let bgRect = CGRect(x: availablePageRect.origin.x + loc.x + padding, y: availablePageRect.origin.y + currentY, 761 | width: availableSpace.width, height: availableSpace.height) 762 | drawRect(bgRect, fillColor: imageBackgroundColor) 763 | } 764 | } 765 | 766 | let imageX = availablePageRect.origin.x + loc.x + padding + ((availableSpace.width - imageSizeToRender.width) / 2) 767 | let imageY = availablePageRect.origin.y + currentY 768 | let imageRect = CGRect(x: imageX, y: imageY, width: imageSizeToRender.width, height: imageSizeToRender.height) 769 | 770 | if(calculationOnly == false) { 771 | let image = UIImage(contentsOfFile: imagePaths[i]) 772 | image?.draw(in: imageRect) 773 | } 774 | 775 | // advance to next column 776 | loc.x += thisWidth 777 | 778 | let totalHeight = (maxLineHeight + imageSizeToRender.height + spacing) 779 | 780 | if(totalHeight > maxHeightRendered) { 781 | maxHeightRendered = totalHeight 782 | } 783 | } 784 | currentLocation.y += maxHeightRendered 785 | currentLocation.y += defaultSpacing 786 | return funcCallRange 787 | } 788 | 789 | fileprivate func addAttributedStringsToColumns(_ columnWidths: [CGFloat], strings: [NSAttributedString], horizontalPadding: CGFloat = 5, allowSplitting: Bool = true, boxStyle: BoxStyle? = nil, calculationOnly: Bool = false, withVerticalDividerLine: Bool = false) -> NSRange { 790 | assert(columnWidths.count == strings.count, "columnWidths and strings array don't have same number of elements") 791 | 792 | var funcCallRange = NSMakeRange(0, 0) 793 | 794 | var ranges = Array() // tracks range for each column 795 | var framesetters = Array() // tracks framsetter for each column 796 | for i in 0 ..< strings.count { 797 | ranges.append(CFRangeMake(0, 0)) 798 | framesetters.append(CTFramesetterCreateWithAttributedString(strings[i])) 799 | } 800 | 801 | var availableSpace = CGSize.zero 802 | if((availablePageRect.size.height - currentLocation.y) <= 0) { 803 | funcCallRange.location = 1 804 | startNewPage(calculationOnly) 805 | } 806 | else { 807 | // decide if we start start a new page 808 | if(allowSplitting == false) { 809 | var allStringsFitOnThisPage = true 810 | for i in 0 ..< ranges.count { 811 | let thisWidth = columnWidths[i] 812 | let thisString = strings[i] 813 | 814 | availableSpace = CGSize(width: thisWidth - (2 * horizontalPadding), height: availablePageRect.size.height - currentLocation.y) 815 | 816 | if canFitAttributedString(thisString, size: availableSpace) == false { 817 | allStringsFitOnThisPage = false 818 | break 819 | } 820 | } 821 | 822 | if(allStringsFitOnThisPage == false) { 823 | var allStringsFitOnANewPage = true 824 | for i in 0 ..< ranges.count { 825 | let thisWidth = columnWidths[i] 826 | let thisString = strings[i] 827 | 828 | availableSpace = CGSize(width: thisWidth - (2 * horizontalPadding), height: availablePageRect.size.height) 829 | 830 | if canFitAttributedString(thisString, size: availableSpace) == false { 831 | allStringsFitOnANewPage = false 832 | break 833 | } 834 | } 835 | 836 | if(allStringsFitOnANewPage) { 837 | startNewPage(calculationOnly) 838 | funcCallRange.location = 1 839 | } 840 | } 841 | } 842 | } 843 | 844 | var done = false 845 | repeat { 846 | var loc = currentLocation 847 | var maxHeightRendered:CGFloat = 0 848 | 849 | for i in 0 ..< columnWidths.count { 850 | var thisRange = ranges[i] 851 | let thisFramesetter = framesetters[i] 852 | let thisWidth = columnWidths[i] 853 | let thisString = strings[i] 854 | // skip the column if it has been rendered completely 855 | if(thisRange.location >= thisString.length) { 856 | loc.x += thisWidth 857 | continue 858 | } 859 | 860 | availableSpace = CGSize(width: thisWidth - (2 * horizontalPadding), height: availablePageRect.size.height - loc.y) 861 | // if height is -ve, CTFramesetterSuggestFrameSizeWithConstraints doesn't return an empty fitRange 862 | if(availableSpace.height <= 0) { 863 | break 864 | } 865 | var fitRange = CFRangeMake(0, 0) 866 | let suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(thisFramesetter, thisRange, nil, availableSpace, &fitRange) 867 | 868 | // draw string 869 | var originalTextRect = CGRect(x: availablePageRect.origin.x + loc.x, y: availablePageRect.origin.y + loc.y, 870 | width: thisWidth, height: availableSpace.height) 871 | if(calculationOnly == false) { 872 | let boxRect = CGRect(x: originalTextRect.origin.x, y: originalTextRect.origin.y, width: availableSpace.width, height: suggestedSize.height) 873 | 874 | if let backgroundColor = boxStyle?.backgroundColor { 875 | drawRect(boxRect, fillColor: backgroundColor) 876 | } 877 | 878 | for border in boxStyle?.boders ?? [] { 879 | if border.borderEdges.contains(.Top) { 880 | drawLine(boxRect.topLeftCorner(), p2: boxRect.topRightCorner(), color: border.color, strokeWidth: border.width) 881 | } 882 | if border.borderEdges.contains(.Left) { 883 | drawLine(boxRect.topLeftCorner(), p2: boxRect.bottomLeftCorner(), color: border.color, strokeWidth: border.width) 884 | } 885 | if border.borderEdges.contains(.Right) { 886 | drawLine(boxRect.topRightCorner(), p2: boxRect.bottomRightCorner(), color: border.color, strokeWidth: border.width) 887 | } 888 | if border.borderEdges.contains(.Bottom) { 889 | drawLine(boxRect.bottomLeftCorner(), p2: boxRect.bottomRightCorner(), color: border.color, strokeWidth: border.width) 890 | } 891 | } 892 | 893 | if withVerticalDividerLine && i < columnWidths.count - 1 { 894 | let x = originalTextRect.origin.x + availableSpace.width + horizontalPadding 895 | drawLine(CGPoint(x: x, y: originalTextRect.origin.y), p2: CGPoint(x: x, y: originalTextRect.origin.y + suggestedSize.height)) 896 | } 897 | } 898 | 899 | originalTextRect.origin.x += horizontalPadding 900 | originalTextRect.size.width = availableSpace.width 901 | 902 | let textRect = convertRectToCoreTextCoordinates(originalTextRect) 903 | 904 | if(calculationOnly == false) { 905 | // flip context 906 | guard let context = UIGraphicsGetCurrentContext() else { 907 | continue 908 | } 909 | 910 | let bounds = UIGraphicsGetPDFContextBounds() 911 | context.textMatrix = CGAffineTransform.identity 912 | context.translateBy(x: bounds.origin.x, y: bounds.size.height) 913 | context.scaleBy(x: 1.0, y: -1.0) 914 | 915 | let textPath = CGMutablePath() 916 | textPath.addRect(textRect) 917 | //CGPathAddRect(textPath, nil, textRect) 918 | let frameRef = CTFramesetterCreateFrame(thisFramesetter, thisRange, textPath, nil) 919 | 920 | CTFrameDraw(frameRef, context) 921 | 922 | // flip it back 923 | context.scaleBy(x: 1.0, y: -1.0) 924 | context.translateBy(x: -bounds.origin.x, y: -bounds.size.height) 925 | } 926 | 927 | thisRange.location = thisRange.location + fitRange.length 928 | ranges[i] = thisRange 929 | 930 | // if we couldn't render whole string, that means we have utilised all avialable height 931 | if(thisRange.location < thisString.length) { 932 | maxHeightRendered = availableSpace.height 933 | } 934 | else { 935 | if(suggestedSize.height >= maxHeightRendered) { 936 | maxHeightRendered = suggestedSize.height 937 | } 938 | } 939 | 940 | loc.x += thisWidth 941 | } 942 | 943 | var shouldAddNewPage = false 944 | 945 | done = true 946 | for i in 0 ..< ranges.count { 947 | let thisRange = ranges[i] 948 | let thisString = strings[i] 949 | if thisRange.location < thisString.length { 950 | done = false 951 | shouldAddNewPage = true 952 | break 953 | } 954 | } 955 | 956 | if(shouldAddNewPage) { 957 | startNewPage(calculationOnly) 958 | funcCallRange.length = funcCallRange.length + 1 959 | } 960 | else { 961 | currentLocation.y += maxHeightRendered 962 | currentLocation.y += defaultSpacing 963 | } 964 | 965 | } while (!done) 966 | return funcCallRange 967 | } 968 | 969 | 970 | fileprivate func addAttributedString(_ attrString: NSAttributedString, allowSplitting:Bool = true, boxStyle: BoxStyle? = nil, calculationOnly: Bool = false) -> NSRange { 971 | return addAttributedStringsToColumns([availablePageRect.size.width], strings: [attrString], horizontalPadding: 0.0, allowSplitting: allowSplitting, boxStyle: boxStyle, calculationOnly: calculationOnly) 972 | } 973 | 974 | fileprivate func addView(_ view: UIView, calculationOnly: Bool = false) -> NSRange { 975 | // Here's how I work with NSRange in these functions 976 | // location is startPageOffset 977 | // length is how many pages are added, so (length + location) = last page index 978 | 979 | var range = NSMakeRange(0, 0) // a view is always 980 | if(currentLocation.y > 0) { 981 | range.location = 1 982 | startNewPage(calculationOnly) 983 | } 984 | 985 | if(calculationOnly == false) { 986 | guard let context = UIGraphicsGetCurrentContext() else { 987 | return range 988 | } 989 | 990 | view.layer.render(in: context) 991 | } 992 | 993 | // one view per page, set Y to maximum so that next call inserts a page 994 | currentLocation.y = availablePageSize.height 995 | return range 996 | } 997 | 998 | fileprivate func addVerticalSpace(_ space: CGFloat, calculationOnly: Bool = false) -> NSRange { 999 | if currentPage == -1 { 1000 | startNewPage(calculationOnly) 1001 | } 1002 | currentLocation.y += space 1003 | return NSMakeRange(0, 0) 1004 | } 1005 | 1006 | fileprivate func drawTableofContents(_ document:DocumentStructure, calculationOnly:Bool = true) -> NSRange { 1007 | if(document.tableOfContents.count == 0) { 1008 | return NSMakeRange(0, 0) 1009 | } 1010 | 1011 | var funcRange = NSMakeRange(0, 0) 1012 | if(currentLocation.y > 0) { 1013 | funcRange.location = 1 1014 | startNewPage(calculationOnly) 1015 | } 1016 | 1017 | let headingRange = addHeadline("Table of Contents", style: .h3, backgroundBoxColor: nil, calculationOnly: calculationOnly) 1018 | funcRange.length += (headingRange.location + headingRange.length) 1019 | for i in 0 ..< document.tableOfContents.count { 1020 | let tocNode = document.tableOfContents[i] 1021 | 1022 | // NOTE: on very first call to this function, page numbers would not be correct in table of contents because we don't know 1023 | // how many pages TOC would take. It does not matter however because the very first call would be "calculationOnly" anyway 1024 | var tocAdjustedPageNumber = tocNode.pageIndex 1025 | if(tocNode.pageIndex >= document.tableOfContentsOnPage) { 1026 | tocAdjustedPageNumber += (document.tableOfContentsPagesRange.location + document.tableOfContentsPagesRange.length) 1027 | } 1028 | 1029 | let pageNumberAttrString = NSMutableAttributedString(string: "\(tocAdjustedPageNumber + 1)") 1030 | let paragraphStyle = NSMutableParagraphStyle() 1031 | paragraphStyle.alignment = .right 1032 | pageNumberAttrString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, pageNumberAttrString.length)) 1033 | 1034 | let col2Width:CGFloat = 50 1035 | let col1Width = availablePageSize.width - col2Width 1036 | 1037 | let range = addAttributedStringsToColumns([col1Width, col2Width], strings: [tocNode.attrString, pageNumberAttrString], horizontalPadding: 5, allowSplitting: false, boxStyle: nil, calculationOnly: calculationOnly) 1038 | 1039 | funcRange.length += (range.location + range.length) 1040 | } 1041 | 1042 | // this is to force a page break before new element 1043 | currentLocation.y = availablePageSize.height 1044 | 1045 | return funcRange 1046 | } 1047 | 1048 | 1049 | // MARK: - Drawing 1050 | fileprivate func drawRect(_ rect: CGRect, fillColor fillColorIn: UIColor? = nil) { 1051 | let fillColor = fillColorIn ?? UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) 1052 | guard let context = UIGraphicsGetCurrentContext() else { 1053 | return 1054 | } 1055 | 1056 | context.setStrokeColor(red: 0, green: 0, blue: 0, alpha: 1) 1057 | context.setFillColor(fillColor.cgColor) 1058 | context.fill(rect) 1059 | //CGContextStrokeRect(context, rect) 1060 | } 1061 | 1062 | fileprivate func drawLine(_ p1: CGPoint, p2:CGPoint, color: UIColor? = nil, strokeWidth: CGFloat = 0.5) { 1063 | var color = color 1064 | if(color == nil) { 1065 | color = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) 1066 | } 1067 | guard let context = UIGraphicsGetCurrentContext() else { 1068 | return 1069 | } 1070 | 1071 | context.setStrokeColor(color!.cgColor) 1072 | context.setLineWidth(strokeWidth) 1073 | context.move(to: CGPoint(x: p1.x, y: p1.y)) 1074 | context.addLine(to: CGPoint(x: p2.x, y: p2.y)) 1075 | context.drawPath(using: CGPathDrawingMode.stroke) 1076 | } 1077 | 1078 | // MARK: - Utilities 1079 | fileprivate func canFitAttributedString(_ attrString: NSAttributedString, size:CGSize) -> Bool { 1080 | // if height is -ve, CTFramesetterSuggestFrameSizeWithConstraints doesn't return an empty fitRange 1081 | if(size.height <= 0) { 1082 | return false 1083 | } 1084 | 1085 | let frameSetter = CTFramesetterCreateWithAttributedString(attrString) 1086 | var fitRangeFullSize = CFRangeMake(0, 0) 1087 | CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, size, &fitRangeFullSize) 1088 | if(fitRangeFullSize.length < attrString.length) { 1089 | return false 1090 | } 1091 | return true 1092 | } 1093 | 1094 | fileprivate func convertRectToCoreTextCoordinates(_ rectIn: CGRect) -> CGRect { 1095 | var r = rectIn 1096 | let bounds = getPageBounds() 1097 | r.origin.y = bounds.size.height - (r.size.height + r.origin.y) 1098 | return r 1099 | } 1100 | 1101 | fileprivate func convertPointToCoreTextCoordinates(_ pointIn: CGPoint) -> CGPoint { 1102 | var p = pointIn 1103 | let bounds = getPageBounds() 1104 | p.y = bounds.size.height - p.y 1105 | return p 1106 | } 1107 | 1108 | fileprivate func aspectFitHeightForWidth(_ size: CGSize, width: CGFloat) -> CGFloat { 1109 | let ratio = size.width / width 1110 | let newHeight = size.height / ratio 1111 | return newHeight 1112 | } 1113 | 1114 | fileprivate func aspectFitWidthForHeight(_ size: CGSize, height: CGFloat) -> CGFloat { 1115 | let ratio = size.height / height 1116 | let newWidth = size.width / ratio 1117 | return newWidth 1118 | } 1119 | 1120 | fileprivate func getPageBounds() -> CGRect { 1121 | let size = self.pageSize.asCGSize() 1122 | if(pageOrientation == .portrait) { 1123 | return CGRect(origin: CGPoint.zero, size: size) 1124 | } 1125 | else { 1126 | return CGRect(origin: CGPoint.zero, size: CGSize(width: size.height, height: size.width)) 1127 | } 1128 | } 1129 | } 1130 | 1131 | // MARK: - PageSize 1132 | public enum PageSize { 1133 | case letter 1134 | case a4 1135 | case custom(size: CGSize) 1136 | 1137 | func asCGSize() -> CGSize { 1138 | switch(self) { 1139 | case .letter: 1140 | return CGSize(width: 612, height: 792) 1141 | case .a4: 1142 | return CGSize(width: 595, height: 842) 1143 | case .custom(let size): 1144 | return size 1145 | } 1146 | } 1147 | } 1148 | 1149 | // MARK: - PageOrientation 1150 | public enum PageOrientation { 1151 | case portrait 1152 | case landscape 1153 | } 1154 | 1155 | // MARK: - HeaderFooterType 1156 | public enum HeaderFooterType { 1157 | case header 1158 | case footer 1159 | } 1160 | 1161 | // MARK: - HeaderFooterText 1162 | public struct HeaderFooterText { 1163 | var type: HeaderFooterType 1164 | var pageRange: NSRange 1165 | var attributedString: NSAttributedString 1166 | 1167 | public init(type: HeaderFooterType, pageRange: NSRange = NSMakeRange(0, Int.max), attributedString: NSAttributedString) { 1168 | self.type = type 1169 | self.pageRange = pageRange 1170 | self.attributedString = attributedString 1171 | } 1172 | } 1173 | 1174 | // MARK: - HeaderFooterImage 1175 | public struct HeaderFooterImage { 1176 | let type: HeaderFooterType 1177 | let pageRange: NSRange 1178 | var imagePath: String 1179 | var image: UIImage? 1180 | var imageHeight: CGFloat 1181 | var alignment: NSTextAlignment 1182 | 1183 | public init(type: HeaderFooterType, pageRange: NSRange = NSMakeRange(0, Int.max), imagePath: String = "", image: UIImage? = nil, imageHeight: CGFloat, alignment: NSTextAlignment = .left) { 1184 | self.type = type 1185 | self.pageRange = pageRange 1186 | self.imagePath = imagePath 1187 | self.image = image 1188 | self.imageHeight = imageHeight 1189 | self.alignment = alignment 1190 | } 1191 | } 1192 | 1193 | // MARK: - SimplePDF vars 1194 | fileprivate (set) open var textFormatter: DefaultTextFormatter 1195 | fileprivate (set) open var pageSize: PageSize 1196 | fileprivate (set) open var pageOrientation: PageOrientation 1197 | fileprivate (set) open var leftMargin:CGFloat 1198 | fileprivate (set) open var rightMargin: CGFloat 1199 | fileprivate (set) open var topMargin: CGFloat 1200 | fileprivate (set) open var bottomMargin: CGFloat 1201 | 1202 | open var headerFooterTexts = Array() 1203 | open var headerFooterImages = Array() 1204 | 1205 | open var availablePageSize: CGSize { 1206 | get { return pdfWriter.availablePageSize } 1207 | } 1208 | 1209 | open var pageIndexToShowTOC: Int { 1210 | get { return document.tableOfContentsOnPage } 1211 | set { document.tableOfContentsOnPage = newValue } 1212 | } 1213 | 1214 | open var biggestHeadingToIncludeInTOC: TextStyle { 1215 | get { return document.biggestHeadingToIncludeInTOC } 1216 | set { document.biggestHeadingToIncludeInTOC = newValue } 1217 | } 1218 | open var smallestHeadingToIncludeInTOC: TextStyle { 1219 | get { return document.smallestHeadingToIncludeInTOC } 1220 | set { document.smallestHeadingToIncludeInTOC = newValue } 1221 | } 1222 | 1223 | open let pdfFilePath: String 1224 | open let pdfFileName: String 1225 | open let authorName: String 1226 | open let pdfTitle: String 1227 | 1228 | fileprivate var document = DocumentStructure() 1229 | fileprivate var pdfWriter: PDFWriter 1230 | 1231 | open var currentPage : Int { 1232 | get { return self.pdfWriter.currentPage } 1233 | } 1234 | 1235 | /* 1236 | private var pagesCount: Int { 1237 | get { return self.document.pagesCount 1238 | } 1239 | }*/ 1240 | 1241 | // MARK: - SimplePDF methods 1242 | public init(pdfTitle: String, authorName: String, fileName : String = "SimplePDF.pdf", replaceFileIfExisting: Bool = false, pageSize: PageSize = .a4, pageOrientation: PageOrientation = .portrait, 1243 | leftMargin:CGFloat = 36, rightMargin:CGFloat = 36, topMargin: CGFloat = 72, bottomMargin: CGFloat = 36, textFormatter: DefaultTextFormatter = DefaultTextFormatter()) { 1244 | 1245 | self.leftMargin = leftMargin 1246 | self.rightMargin = rightMargin 1247 | self.topMargin = topMargin 1248 | self.bottomMargin = bottomMargin 1249 | self.pageSize = pageSize 1250 | self.pageOrientation = pageOrientation 1251 | self.textFormatter = textFormatter 1252 | 1253 | self.authorName = authorName 1254 | self.pdfTitle = pdfTitle 1255 | 1256 | self.pdfFileName = fileName 1257 | let tmpFilePath = SimplePDFUtilities.pathForTmpFile(pdfFileName) 1258 | self.pdfFilePath = replaceFileIfExisting ? tmpFilePath : SimplePDFUtilities.renameFilePathToPreventNameCollissions(tmpFilePath as NSString) 1259 | 1260 | self.pdfWriter = PDFWriter(textFormatter: textFormatter, pageSize: pageSize, pageOrientation: pageOrientation, 1261 | leftMargin: leftMargin, rightMargin: rightMargin, topMargin: topMargin, bottomMargin: bottomMargin, 1262 | pagesCount: 0, 1263 | headerFooterTexts: headerFooterTexts, headerFooterImages: headerFooterImages) 1264 | } 1265 | 1266 | fileprivate func initializePDFWriter(_ pagesCount: Int) -> PDFWriter { 1267 | return PDFWriter(textFormatter: textFormatter, pageSize: pageSize, pageOrientation: pageOrientation, 1268 | leftMargin: leftMargin, rightMargin: rightMargin, topMargin: topMargin, bottomMargin: bottomMargin, 1269 | pagesCount: pagesCount, 1270 | headerFooterTexts: headerFooterTexts, headerFooterImages: headerFooterImages) 1271 | } 1272 | 1273 | open func writePDFWithoutTableOfContents() -> String { 1274 | 1275 | /////////// CACULATIONS PASS /////////// 1276 | // Start with a clean slate 1277 | self.pdfWriter = initializePDFWriter(0) 1278 | var pageIndex = -1 1279 | for docElement in document.documentElements { 1280 | // page number is: let pageNumber = pageIndex + docElement.pageRange.location 1281 | docElement.executeFunctionCall(pdfWriter, calculationOnly: true) 1282 | pageIndex += (docElement.pageRange.location + docElement.pageRange.length) 1283 | } 1284 | 1285 | /////////// RENDERING PASS /////////// 1286 | self.pdfWriter = initializePDFWriter(pageIndex+1) 1287 | // 1. create context 1288 | pdfWriter.openPDF(pdfFilePath, title: pdfTitle, author: authorName) 1289 | pageIndex = -1 1290 | 1291 | for docElement in document.documentElements { 1292 | // page number is: let pageNumber = pageIndex + docElement.pageRange.location 1293 | docElement.executeFunctionCall(pdfWriter, calculationOnly: false) 1294 | pageIndex += (docElement.pageRange.location + docElement.pageRange.length) 1295 | } 1296 | 1297 | // 3. end pdf context 1298 | pdfWriter.closePDF() 1299 | return pdfFilePath 1300 | } 1301 | 1302 | 1303 | open func writePDFWithTableOfContents() -> String { 1304 | /////////// CACULATIONS PASS /////////// 1305 | // Start with a clean slate 1306 | self.pdfWriter = initializePDFWriter(0) 1307 | 1308 | // Generate TOC data structure 1309 | // todo: empty toc structure here 1310 | document.tableOfContents = document.generateTableOfContents() 1311 | var tocInserted = false 1312 | var pageIndex = -1 1313 | for docElement in document.documentElements { 1314 | let pageNumber = pageIndex + docElement.pageRange.location 1315 | // if (location == 1 && pageNumber == document.tableOfContentsOnPage) || (location == 0 && pageNumber > document.tableOfContents) { 1316 | if(pageNumber >= document.tableOfContentsOnPage && tocInserted == false) { 1317 | tocInserted = true 1318 | document.tableOfContentsPagesRange = pdfWriter.drawTableofContents(document, calculationOnly: true) 1319 | pageIndex += (document.tableOfContentsPagesRange.location + document.tableOfContentsPagesRange.length) 1320 | } 1321 | 1322 | docElement.executeFunctionCall(pdfWriter, calculationOnly: true) 1323 | pageIndex += (docElement.pageRange.location + docElement.pageRange.length) 1324 | } 1325 | 1326 | if(tocInserted == false) { 1327 | tocInserted = true 1328 | document.tableOfContentsPagesRange = pdfWriter.drawTableofContents(document, calculationOnly: true) 1329 | pageIndex += (document.tableOfContentsPagesRange.location + document.tableOfContentsPagesRange.length) 1330 | } 1331 | 1332 | /////////// RECALCULATE TOC -- AFTER WE HAVE UPDATED THE PAGE RANGES BY INSERTING TOC /////// 1333 | // todo: fill in toc data structure with page numbers here 1334 | document.tableOfContents = document.generateTableOfContents() 1335 | 1336 | /////////// RENDERING PASS /////////// 1337 | self.pdfWriter = initializePDFWriter(pageIndex+1) 1338 | // 1. create context 1339 | pdfWriter.openPDF(pdfFilePath, title: pdfTitle, author: authorName) 1340 | 1341 | tocInserted = false 1342 | pageIndex = -1 1343 | 1344 | for docElement in document.documentElements { 1345 | let pageNumber = pageIndex + docElement.pageRange.location 1346 | if(pageNumber >= document.tableOfContentsOnPage && tocInserted == false) { 1347 | tocInserted = true 1348 | document.tableOfContentsPagesRange = pdfWriter.drawTableofContents(document, calculationOnly: false) 1349 | pageIndex += (document.tableOfContentsPagesRange.location + document.tableOfContentsPagesRange.length) 1350 | } 1351 | 1352 | docElement.executeFunctionCall(pdfWriter, calculationOnly: false) 1353 | pageIndex += (docElement.pageRange.location + docElement.pageRange.length) 1354 | } 1355 | if(tocInserted == false) { 1356 | tocInserted = true 1357 | document.tableOfContentsPagesRange = pdfWriter.drawTableofContents(document, calculationOnly: false) 1358 | pageIndex += (document.tableOfContentsPagesRange.location + document.tableOfContentsPagesRange.length) 1359 | } 1360 | 1361 | // 3. end pdf context 1362 | pdfWriter.closePDF() 1363 | return pdfFilePath 1364 | } 1365 | 1366 | // MARK: - Commands 1367 | // NOTE: these functions should only be called by consumers of the class, don't call them internally because they change 1368 | // the document structure 1369 | @discardableResult open func startNewPage() -> NSRange { 1370 | let range = pdfWriter.startNewPage(true) 1371 | let funcCall = DocumentStructure.FunctionCall.startNewPage 1372 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1373 | document.documentElements.append(docNode) 1374 | return range 1375 | } 1376 | 1377 | @discardableResult open func addH1(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1378 | let range = pdfWriter.addHeadline(string, style: .h1, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1379 | let funcCall = DocumentStructure.FunctionCall.addH1(string: string, backgroundBoxColor: backgroundBoxColor) 1380 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1381 | document.documentElements.append(docNode) 1382 | return range 1383 | } 1384 | 1385 | @discardableResult open func addH2(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1386 | let range = pdfWriter.addHeadline(string, style: .h2, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1387 | let funcCall = DocumentStructure.FunctionCall.addH2(string: string, backgroundBoxColor: backgroundBoxColor) 1388 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1389 | document.documentElements.append(docNode) 1390 | return range 1391 | } 1392 | 1393 | @discardableResult open func addH3(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1394 | let range = pdfWriter.addHeadline(string, style: .h3, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1395 | let funcCall = DocumentStructure.FunctionCall.addH3(string: string, backgroundBoxColor: backgroundBoxColor) 1396 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1397 | document.documentElements.append(docNode) 1398 | return range 1399 | } 1400 | 1401 | @discardableResult open func addH4(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1402 | let range = pdfWriter.addHeadline(string, style: .h4, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1403 | let funcCall = DocumentStructure.FunctionCall.addH4(string: string, backgroundBoxColor: backgroundBoxColor) 1404 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1405 | document.documentElements.append(docNode) 1406 | return range 1407 | } 1408 | 1409 | @discardableResult open func addH5(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1410 | let range = pdfWriter.addHeadline(string, style: .h5, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1411 | let funcCall = DocumentStructure.FunctionCall.addH5(string: string, backgroundBoxColor: backgroundBoxColor) 1412 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1413 | document.documentElements.append(docNode) 1414 | return range 1415 | } 1416 | 1417 | @discardableResult open func addH6(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1418 | let range = pdfWriter.addHeadline(string, style: .h6, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1419 | let funcCall = DocumentStructure.FunctionCall.addH6(string: string, backgroundBoxColor: backgroundBoxColor) 1420 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1421 | document.documentElements.append(docNode) 1422 | return range 1423 | } 1424 | 1425 | @discardableResult open func addBodyText(_ string: String, backgroundBoxColor: UIColor? = nil) -> NSRange { 1426 | let range = pdfWriter.addBodyText(string, backgroundBoxColor: backgroundBoxColor, calculationOnly: true) 1427 | let funcCall = DocumentStructure.FunctionCall.addBodyText(string: string, backgroundBoxColor: backgroundBoxColor) 1428 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1429 | document.documentElements.append(docNode) 1430 | return range 1431 | } 1432 | 1433 | @discardableResult open func addImages(_ imagePaths:[String], imageCaptions: [String], imagesPerRow:Int = 3, spacing:CGFloat = 2, padding:CGFloat = 5) -> NSRange { 1434 | let range = pdfWriter.addImages(imagePaths: imagePaths, imageCaptions: imageCaptions, imagesPerRow: imagesPerRow, spacing: spacing, padding: padding, calculationOnly: true) 1435 | let funcCall = DocumentStructure.FunctionCall.addImages(imagePaths: imagePaths, imageCaptions: imageCaptions, imagesPerRow: imagesPerRow, spacing: spacing, padding: padding) 1436 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1437 | document.documentElements.append(docNode) 1438 | return range 1439 | } 1440 | 1441 | open func addImagesRow(_ imagePaths: [String], imageCaptions: [NSAttributedString], columnWidths: [CGFloat], 1442 | spacing: CGFloat = 2, padding: CGFloat = 5, captionBackgroundColor: UIColor? = nil, imageBackgroundColor: UIColor? = nil) -> NSRange { 1443 | let range = pdfWriter.addImagesRow(imagePaths, imageCaptions: imageCaptions, columnWidths: columnWidths, spacing: spacing, padding: padding, captionBackgroundColor: captionBackgroundColor, imageBackgroundColor: imageBackgroundColor, calculationOnly: true) 1444 | let funcCall = DocumentStructure.FunctionCall.addImagesRow(imagePaths: imagePaths, imageCaptions: imageCaptions, columnWidths: columnWidths, spacing: spacing, padding: padding, captionBackgroundColor: captionBackgroundColor, imageBackgroundColor: imageBackgroundColor) 1445 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1446 | self.document.documentElements.append(docNode) 1447 | return range 1448 | } 1449 | 1450 | open func addAttributedStringsToColumns(_ columnWidths: [CGFloat], strings: [NSAttributedString], horizontalPadding: CGFloat = 5, allowSplitting: Bool = true, backgroundColor: UIColor? = nil, withVerticalDividerLine : Bool = false) -> NSRange { 1451 | let boxStyle = BoxStyle(boders: nil, backgroundColor: backgroundColor) 1452 | var range = pdfWriter.addAttributedStringsToColumns(columnWidths, strings: strings, horizontalPadding: horizontalPadding, allowSplitting: allowSplitting, boxStyle: boxStyle, calculationOnly: true, withVerticalDividerLine: withVerticalDividerLine) 1453 | if range.location > 0 { 1454 | moveHeadlinesToNewPage() 1455 | // recalculate the range due to adding page break befor headlines 1456 | range = pdfWriter.addAttributedStringsToColumns(columnWidths, strings: strings, horizontalPadding: horizontalPadding, allowSplitting: allowSplitting, boxStyle: boxStyle, calculationOnly: true, withVerticalDividerLine: withVerticalDividerLine) 1457 | } 1458 | let funcCall = DocumentStructure.FunctionCall.addAttributedStringsToColumns(columnWidths: columnWidths, strings: strings, horizontalPadding: horizontalPadding, allowSplitting: allowSplitting, boxStyle: boxStyle, withVerticalDividerLine: withVerticalDividerLine) 1459 | let docNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1460 | self.document.documentElements.append(docNode) 1461 | return range 1462 | } 1463 | 1464 | @discardableResult open func addAttributedString(_ attrString: NSAttributedString, allowSplitting:Bool = true, backgroundBoxColor: UIColor? = nil) -> NSRange { 1465 | return addAttributedStringsToColumns([pdfWriter.availablePageRect.size.width], strings: [attrString], horizontalPadding: 0.0, allowSplitting: allowSplitting, backgroundColor: backgroundBoxColor) 1466 | } 1467 | 1468 | fileprivate func moveHeadlinesToNewPage() { 1469 | if let prevDocNode = self.document.documentElements.last { 1470 | switch prevDocNode.functionCall { 1471 | case .addH1, .addH2, .addH3, .addH4, .addH5, .addH6: 1472 | var newPageDocElementIndex = document.documentElements.endIndex - 1 1473 | // check for higher level headlines, e.g. if the a text in section 3.1.1 also move healines 3. and 3.1. to the new page 1474 | while self.document.documentElements[newPageDocElementIndex-1].functionCall.contentLevel < self.document.documentElements[newPageDocElementIndex].functionCall.contentLevel { 1475 | newPageDocElementIndex -= 1 1476 | } 1477 | 1478 | let newPageRange = pdfWriter.startNewPage(true) 1479 | let newPageFuncCall = DocumentStructure.FunctionCall.startNewPage 1480 | let newPageNode = DocumentStructure.DocumentElement(functionCall: newPageFuncCall, pageRange: newPageRange) 1481 | document.documentElements.insert(newPageNode, at: newPageDocElementIndex) 1482 | default: break; 1483 | } 1484 | } 1485 | } 1486 | 1487 | // This function can be used to render a view to a PDF page (mostly useful to design cover pages). A view is always added to its own page. It starts 1488 | // a new page if required, and any content added after it appears on the next page. 1489 | // 1490 | // Here's how you can design a cover page with a UIView (sample applies to any other view that you want to add to pdf) 1491 | // 1. Create a nib with the same dimensions as PDF page (e.g. A4 page is 595x842) 1492 | // 2. All the labels in the view should have their class set to `SimplePDFLabel` (or a subclass of it) 1493 | // 3. Load the view from the nib and add it to pdf 1494 | // ``` 1495 | // // ... 1496 | // let coverPage = NSBundle.mainBundle().loadNibNamed("PDFCoverPage", owner: self, options: nil).first as PDFCoverPage 1497 | // pdf.addView(coverPage) 1498 | // ``` 1499 | // 1500 | // NOTE: 1501 | // Please note that if you use the above method to render a view to PDF, AutoLayout will *not* be run on it, If your view doesn't rely on 1502 | // autolayout e.g. say it's a table, may be an invoice?, you don't need to worry about anything. 1503 | // 1504 | // However, if your view uses AutoLayout to correctly position elements, you *have to* add it to the active view hierarchy. You can add to the 1505 | // view hierarchy off-screen, then call `pdf.addView()` to render it to PDF. The catch here is that now the view would render as *bitmap*. This means 1506 | // any labels will not be selectable as text and they would lose quality if you zoom in (because they are bitmaps). 1507 | // 1508 | 1509 | open func addView(_ view: UIView) -> NSRange { 1510 | let range = pdfWriter.addView(view, calculationOnly: true) 1511 | let funcCall = DocumentStructure.FunctionCall.addView(view: view) 1512 | let documentNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1513 | self.document.documentElements.append(documentNode) 1514 | return range 1515 | } 1516 | 1517 | open func addVerticalSpace(_ space: CGFloat) -> NSRange { 1518 | let range = pdfWriter.addVerticalSpace(space, calculationOnly: true) 1519 | let funcCall = DocumentStructure.FunctionCall.addVerticalSpace(space: space) 1520 | let documentNode = DocumentStructure.DocumentElement(functionCall: funcCall, pageRange: range) 1521 | self.document.documentElements.append(documentNode) 1522 | return range 1523 | } 1524 | } 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | // MARK: - Backwards compatibility - 1532 | extension SimplePDF { 1533 | @available(*, deprecated, message: "use `SimplePDF.pageNumberPlaceholder` instead") 1534 | public var kPageNumberPlaceholder : String { get { return SimplePDF.pageNumberPlaceholder }} 1535 | 1536 | @available(*, deprecated, message: "use `SimplePDF.pagesCountPlaceholder` instead") 1537 | public var kPagesCountPlaceholder : String { get { return SimplePDF.pagesCountPlaceholder }} 1538 | 1539 | @available(*, deprecated, message: "use `SimplePDF.defaultSpacing` instead") 1540 | public var kStandardSpacing : CGFloat { get { return SimplePDF.defaultSpacing }} 1541 | 1542 | @available(*, deprecated, message: "use `SimplePDF.defaultBackgroundBoxColor` instead") 1543 | public var kDefaultBackgroundBoxColor : UIColor { get { return SimplePDF.defaultBackgroundBoxColor }} 1544 | } 1545 | 1546 | // MARK: - 1547 | 1548 | 1549 | extension CGRect { 1550 | func topRightCorner() -> CGPoint { 1551 | return CGPoint(x: self.minX, y: self.minY) 1552 | } 1553 | func topLeftCorner() -> CGPoint { 1554 | return CGPoint(x: self.maxX, y: self.minY) 1555 | } 1556 | func bottomRightCorner() -> CGPoint { 1557 | return CGPoint(x: self.minX, y: self.maxY) 1558 | } 1559 | func bottomLeftCorner() -> CGPoint { 1560 | return CGPoint(x: self.maxX, y: self.maxY) 1561 | } 1562 | } 1563 | 1564 | 1565 | --------------------------------------------------------------------------------