├── 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 |
--------------------------------------------------------------------------------