├── .gitignore ├── .swift-version ├── Assets ├── example.pdf ├── page_setup.png ├── simple_pdf.key └── simple_pdf_logo.png ├── ExampleCode.swift ├── LICENSE ├── README.md ├── SimplePDF.podspec ├── SimplePDF.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SimplePDF ├── Info.plist ├── SimplePDF.h └── SimplePDF.swift └── SimplePDFTests ├── Info.plist ├── SimplePDFTests.swift └── simple_pdf_logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | 24 | ## Other 25 | *.xccheckout 26 | *.moved-aside 27 | *.xcuserstate 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | 65 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /Assets/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nRewik/SimplePDF/35e3e7ea6500eaf9244d66b9e6d316f7b85072aa/Assets/example.pdf -------------------------------------------------------------------------------- /Assets/page_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nRewik/SimplePDF/35e3e7ea6500eaf9244d66b9e6d316f7b85072aa/Assets/page_setup.png -------------------------------------------------------------------------------- /Assets/simple_pdf.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nRewik/SimplePDF/35e3e7ea6500eaf9244d66b9e6d316f7b85072aa/Assets/simple_pdf.key -------------------------------------------------------------------------------- /Assets/simple_pdf_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nRewik/SimplePDF/35e3e7ea6500eaf9244d66b9e6d316f7b85072aa/Assets/simple_pdf_logo.png -------------------------------------------------------------------------------- /ExampleCode.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SimplePDF 3 | 4 | let a4PaperSize = CGSize(width: 595, height: 842) 5 | let pdf = SimplePDF(pageSize: a4PaperSize) 6 | 7 | pdf.setContentAlignment(.Center) 8 | 9 | // add logo image 10 | let logoImage = UIImage(named:"simple_pdf_logo")! 11 | pdf.addImage(logoImage) 12 | 13 | pdf.addLineSpace(30) 14 | 15 | pdf.setContentAlignment(.Left) 16 | pdf.addText("Normal text follows by line separator") 17 | pdf.addLineSeparator() 18 | 19 | pdf.addLineSpace(20.0) 20 | 21 | pdf.setContentAlignment(.Right) 22 | pdf.addText("Text after set content alignment to .Right") 23 | pdf.addLineSpace(20.0) 24 | 25 | pdf.addText("Cras quis eros orci.\nLorem ipsum dolor sit amet, consectetur adipiscing elit.\nDonec mollis vitae mi ut lobortis.\nUt ultrices mi vel neque venenatis, ut efficitur metus eleifend. Sed pellentesque lobortis est quis maximus. Maecenas ultricies risus et enim consectetur, id lobortis ante porta. Quisque at euismod enim. Vestibulum faucibus purus non justo fringilla, sit amet iaculis ex pellentesque. Vestibulum eget vulputate diam, sit amet ornare sem. Duis at eros non tortor malesuada accumsan.\nNunc vel libero ut sapien dictum iaculis a vel odio. Quisque purus turpis, tristique auctor ex non, consectetur scelerisque lorem. Mauris est justo, sollicitudin sit amet nisi a, mattis posuere orci. Sed elementum est at est tristique gravida. Aliquam iaculis, metus facilisis varius viverra, nunc libero ultricies arcu, in accumsan sem nibh vel purus.") 26 | 27 | pdf.addLineSpace(30) 28 | 29 | pdf.setContentAlignment(.Center) 30 | 31 | pdf.addText("Center Text") 32 | pdf.addLineSpace(20.0) 33 | pdf.addText("Cras varius leo ac lectus malesuada, ut rhoncus nunc blandit.\n In venenatis diam et vehicula suscipit.\n Aliquam in ante at dolor sodales consectetur ut semper quam.\n Vivamus massa purus, congue sed leo sed, lobortis consectetur lacus. Nunc sed tortor nec augue mattis faucibus. Sed malesuada metus in sapien efficitur, ut aliquet nisl lobortis. Vestibulum faucibus purus non justo fringilla, sit amet iaculis ex pellentesque. Vestibulum eget vulputate diam, sit amet ornare sem. Aliquam erat volutpat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin scelerisque posuere mi, non consequat mauris auctor a. Fusce lacinia auctor lectus a elementum.") 34 | 35 | 36 | pdf.addLineSpace(30.0) 37 | 38 | pdf.setContentAlignment(.Left) 39 | let textString = "This is an example of long text. If the text doesn't fit in the current page. Simple pdf will draw a part of text, and automatically begin a new page to draw the remaining text. This process will be repeated until there's no text left to draw. " 40 | pdf.addText(textString) 41 | 42 | 43 | pdf.beginNewPage() 44 | pdf.addText("Begin new page") 45 | 46 | // Generate PDF data and save to a local file. 47 | if let documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first { 48 | 49 | let fileName = "example.pdf" 50 | let documentsFileName = documentDirectories + "/" + fileName 51 | 52 | let pdfData = pdf.generatePDFdata() 53 | do{ 54 | try pdfData.writeToFile(documentsFileName, options: .DataWritingAtomic) 55 | print("\nThe generated pdf can be found at:") 56 | print("\n\t\(documentsFileName)\n") 57 | }catch{ 58 | print(error) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nutchaphon Rewik 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Simple PDF 3 |

4 | 5 |

6 | Platform: iOS 8.0+ 7 | 8 | Language: Swift 4 9 | 10 | Cocoapods 11 |

12 | 13 | 14 | SimplePDF is a wrapper of UIGraphics PDF context written in Swift. 15 | 16 | You can: 17 | - [x] add texts, images, spaces and lines, table 18 | - [x] set up page layout, adjust content alignment 19 | - [x] generate PDF data/file. 20 | 21 | In summary, you can create a simple PDF effortlessly. :smile: 22 | 23 | ## Example 24 | 25 | ```swift 26 | let A4paperSize = CGSize(width: 595, height: 842) 27 | let pdf = SimplePDF(pageSize: A4paperSize) 28 | 29 | pdf.addText("Hello World!") 30 | // or 31 | // pdf.addText("Hello World!", font: myFont, textColor: myTextColor) 32 | 33 | pdf.addImage( anImage ) 34 | 35 | let dataArray = [["Test1", "Test2"],["Test3", "Test4"]] 36 | pdf.addTable(rowCount: 2, columnCount: 2, rowHeight: 20.0, columnWidth: 30.0, tableLineWidth: 1.0, font: UIFont.systemFontOfSize(5.0), dataArray: dataArray) 37 | 38 | let pdfData = pdf.generatePDFdata() 39 | 40 | // save as a local file 41 | try? pdfData.writeToFile(path, options: .DataWritingAtomic) 42 | ``` 43 | 44 | See the result [example.pdf](Assets/example.pdf), which is generated from [ExampleCode.swift](ExampleCode.swift). 45 | 46 | ## Installation 47 | 48 | via [Cocoapods](https://cocoapods.org) 49 | 50 | ```ruby 51 | use_frameworks! 52 | pod 'SimplePDF' 53 | ``` 54 | 55 | ## Usage 56 | 57 | ```swift 58 | import SimplePDF 59 | ``` 60 | 61 | ### Page Setup 62 | 63 | ![page setup](Assets/page_setup.png) 64 | 65 | ```swift 66 | let A4paperSize = CGSize(width: 595, height: 842) 67 | let pdf = SimplePDF(pageSize: A4paperSize, pageMargin: 20.0) 68 | // or define all margins extra 69 | let pdf = SimplePDF(pageSize: A4paperSize, pageMarginLeft: 35, pageMarginTop: 50, pageMarginBottom: 40, pageMarginRight: 35) 70 | ``` 71 | 72 | ### Write Something 73 | 74 | ```swift 75 | pdf.addText( "some text" ) 76 | pdf.addImage( UIImage ) 77 | pdf.addAttributedText( NSAttributedString ) 78 | pdf.addLineSeparator(height: 30) // or pdf.addLineSeparator() default height is 1.0 79 | pdf.addLineSpace(20) 80 | ``` 81 | 82 | ### Layout 83 | You can layout horizontally and vertically 84 | ```swift 85 | // Start a horizonal arrangement 86 | pdf.beginHorizontalArrangement() 87 | // Add space from the left 88 | pdf.addHorizontalSpace(60) 89 | 90 | // now add your text, table, image, ... 91 | 92 | // finishe the horizontal arrangement so you can continue vertically 93 | pdf.endHorizontalArrangement() 94 | 95 | // adds a vertical space 96 | pdf.addVerticalSpace(70) 97 | ``` 98 | 99 | ### Table Definitions 100 | Define the layout of tables with definitions 101 | ```swift 102 | let tableDef = TableDefinition(alignments: [.left, .left], 103 | columnWidths: [100, 300], 104 | fonts: [UIFont.systemFont(ofSize: 20), 105 | UIFont.systemFont(ofSize: 16)], 106 | textColors: [UIColor.black, 107 | UIColor.blue]) 108 | 109 | let data = [] // my data 110 | 111 | pdf.addTable(data.count, 112 | columnCount: 2, 113 | rowHeight: 25, 114 | tableLineWidth: 0, // this is taken from the definition 115 | tableDefinition: tableDef, 116 | dataArray: data) 117 | ``` 118 | 119 | ### Utilities 120 | 121 | ```swift 122 | pdf.beginNewPage() // Begin a new page 123 | ``` 124 | 125 | These following commands will affect everything you write after you call. 126 | 127 | ```swift 128 | pdf.setContentAlignment(.Center) // .Left, .Center, .Right 129 | ``` 130 | 131 | ### Generate PDF data 132 | 133 | ```swift 134 | let pdfData = pdf.generatePDFdata() 135 | 136 | // write to file 137 | try? pdfData.writeToFile(path, options: .DataWritingAtomic) 138 | 139 | // or upload to Internet 140 | // For example, Alamofire.upload(...) 141 | ``` 142 | 143 | ## License 144 | SimplePDF is available under the [MIT License](LICENSE). 145 | 146 | ## Authors 147 | Nutchaphon Rewik 148 | 149 | [![my twitter][1.1]][1] 150 | 151 | [1.1]: https://img.shields.io/badge/Twitter-@nRewik-blue.svg?style=flat-square 152 | [1]: https://www.twitter.com/nRewik 153 | -------------------------------------------------------------------------------- /SimplePDF.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = "SimplePDF" 4 | spec.version = "3.0.0" 5 | spec.summary = "A library for creating simple pdf files." 6 | spec.homepage = "https://github.com/nrewik/SimplePDF" 7 | spec.license = { type: 'MIT', file: 'LICENSE' } 8 | spec.authors = { "Nutchaphon Rewik" => 'nrewik@outlook.com' } 9 | spec.social_media_url = "http://twitter.com/nrewik" 10 | 11 | spec.platform = :ios, "8.0" 12 | spec.requires_arc = true 13 | spec.source = { git: "https://github.com/nrewik/SimplePDF.git", tag: "v#{spec.version}", submodules: true } 14 | spec.source_files = "SimplePDF/**/*.{h,swift}" 15 | 16 | end 17 | -------------------------------------------------------------------------------- /SimplePDF.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A07943FC1C4B5E5F000ADEFC /* SimplePDF.h in Headers */ = {isa = PBXBuildFile; fileRef = A07943FB1C4B5E5F000ADEFC /* SimplePDF.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | A07944031C4B5E5F000ADEFC /* SimplePDF.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A07943F81C4B5E5F000ADEFC /* SimplePDF.framework */; }; 12 | A07944131C4B5EE4000ADEFC /* SimplePDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07944121C4B5EE4000ADEFC /* SimplePDF.swift */; }; 13 | A07944171C4B5EFE000ADEFC /* simple_pdf_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = A07944151C4B5EFE000ADEFC /* simple_pdf_logo.png */; }; 14 | A079441C1C4B5F6C000ADEFC /* SimplePDFTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A079441B1C4B5F6C000ADEFC /* SimplePDFTests.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | A07944041C4B5E5F000ADEFC /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = A07943EF1C4B5E5F000ADEFC /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = A07943F71C4B5E5F000ADEFC; 23 | remoteInfo = SimplePDF; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | A07943F81C4B5E5F000ADEFC /* SimplePDF.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SimplePDF.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | A07943FB1C4B5E5F000ADEFC /* SimplePDF.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimplePDF.h; sourceTree = ""; }; 30 | A07943FD1C4B5E5F000ADEFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | A07944021C4B5E5F000ADEFC /* SimplePDFTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimplePDFTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | A07944091C4B5E5F000ADEFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | A07944121C4B5EE4000ADEFC /* SimplePDF.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimplePDF.swift; sourceTree = ""; }; 34 | A07944151C4B5EFE000ADEFC /* simple_pdf_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = simple_pdf_logo.png; sourceTree = ""; }; 35 | A079441B1C4B5F6C000ADEFC /* SimplePDFTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimplePDFTests.swift; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | A07943F41C4B5E5F000ADEFC /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | A07943FF1C4B5E5F000ADEFC /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | A07944031C4B5E5F000ADEFC /* SimplePDF.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | A07943EE1C4B5E5F000ADEFC = { 58 | isa = PBXGroup; 59 | children = ( 60 | A07943FA1C4B5E5F000ADEFC /* SimplePDF */, 61 | A07944061C4B5E5F000ADEFC /* SimplePDFTests */, 62 | A07943F91C4B5E5F000ADEFC /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | A07943F91C4B5E5F000ADEFC /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | A07943F81C4B5E5F000ADEFC /* SimplePDF.framework */, 70 | A07944021C4B5E5F000ADEFC /* SimplePDFTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | A07943FA1C4B5E5F000ADEFC /* SimplePDF */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | A07943FB1C4B5E5F000ADEFC /* SimplePDF.h */, 79 | A07944121C4B5EE4000ADEFC /* SimplePDF.swift */, 80 | A07943FD1C4B5E5F000ADEFC /* Info.plist */, 81 | ); 82 | path = SimplePDF; 83 | sourceTree = ""; 84 | }; 85 | A07944061C4B5E5F000ADEFC /* SimplePDFTests */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | A079441B1C4B5F6C000ADEFC /* SimplePDFTests.swift */, 89 | A07944151C4B5EFE000ADEFC /* simple_pdf_logo.png */, 90 | A07944091C4B5E5F000ADEFC /* Info.plist */, 91 | ); 92 | path = SimplePDFTests; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXHeadersBuildPhase section */ 98 | A07943F51C4B5E5F000ADEFC /* Headers */ = { 99 | isa = PBXHeadersBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | A07943FC1C4B5E5F000ADEFC /* SimplePDF.h in Headers */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXHeadersBuildPhase section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | A07943F71C4B5E5F000ADEFC /* SimplePDF */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = A079440C1C4B5E5F000ADEFC /* Build configuration list for PBXNativeTarget "SimplePDF" */; 112 | buildPhases = ( 113 | A07943F31C4B5E5F000ADEFC /* Sources */, 114 | A07943F41C4B5E5F000ADEFC /* Frameworks */, 115 | A07943F51C4B5E5F000ADEFC /* Headers */, 116 | A07943F61C4B5E5F000ADEFC /* Resources */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = SimplePDF; 123 | productName = SimplePDF; 124 | productReference = A07943F81C4B5E5F000ADEFC /* SimplePDF.framework */; 125 | productType = "com.apple.product-type.framework"; 126 | }; 127 | A07944011C4B5E5F000ADEFC /* SimplePDFTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = A079440F1C4B5E5F000ADEFC /* Build configuration list for PBXNativeTarget "SimplePDFTests" */; 130 | buildPhases = ( 131 | A07943FE1C4B5E5F000ADEFC /* Sources */, 132 | A07943FF1C4B5E5F000ADEFC /* Frameworks */, 133 | A07944001C4B5E5F000ADEFC /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | A07944051C4B5E5F000ADEFC /* PBXTargetDependency */, 139 | ); 140 | name = SimplePDFTests; 141 | productName = SimplePDFTests; 142 | productReference = A07944021C4B5E5F000ADEFC /* SimplePDFTests.xctest */; 143 | productType = "com.apple.product-type.bundle.unit-test"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | A07943EF1C4B5E5F000ADEFC /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastSwiftUpdateCheck = 0720; 152 | LastUpgradeCheck = 0900; 153 | ORGANIZATIONNAME = "Nutchaphon Rewik"; 154 | TargetAttributes = { 155 | A07943F71C4B5E5F000ADEFC = { 156 | CreatedOnToolsVersion = 7.2; 157 | LastSwiftMigration = 0900; 158 | }; 159 | A07944011C4B5E5F000ADEFC = { 160 | CreatedOnToolsVersion = 7.2; 161 | LastSwiftMigration = 0900; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = A07943F21C4B5E5F000ADEFC /* Build configuration list for PBXProject "SimplePDF" */; 166 | compatibilityVersion = "Xcode 3.2"; 167 | developmentRegion = English; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | ); 172 | mainGroup = A07943EE1C4B5E5F000ADEFC; 173 | productRefGroup = A07943F91C4B5E5F000ADEFC /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | A07943F71C4B5E5F000ADEFC /* SimplePDF */, 178 | A07944011C4B5E5F000ADEFC /* SimplePDFTests */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | A07943F61C4B5E5F000ADEFC /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | A07944001C4B5E5F000ADEFC /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | A07944171C4B5EFE000ADEFC /* simple_pdf_logo.png in Resources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXResourcesBuildPhase section */ 200 | 201 | /* Begin PBXSourcesBuildPhase section */ 202 | A07943F31C4B5E5F000ADEFC /* Sources */ = { 203 | isa = PBXSourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | A07944131C4B5EE4000ADEFC /* SimplePDF.swift in Sources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | A07943FE1C4B5E5F000ADEFC /* Sources */ = { 211 | isa = PBXSourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | A079441C1C4B5F6C000ADEFC /* SimplePDFTests.swift in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin PBXTargetDependency section */ 221 | A07944051C4B5E5F000ADEFC /* PBXTargetDependency */ = { 222 | isa = PBXTargetDependency; 223 | target = A07943F71C4B5E5F000ADEFC /* SimplePDF */; 224 | targetProxy = A07944041C4B5E5F000ADEFC /* PBXContainerItemProxy */; 225 | }; 226 | /* End PBXTargetDependency section */ 227 | 228 | /* Begin XCBuildConfiguration section */ 229 | A079440A1C4B5E5F000ADEFC /* Debug */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_EMPTY_BODY = YES; 243 | CLANG_WARN_ENUM_CONVERSION = YES; 244 | CLANG_WARN_INFINITE_RECURSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 250 | CLANG_WARN_STRICT_PROTOTYPES = YES; 251 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 252 | CLANG_WARN_UNREACHABLE_CODE = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 255 | COPY_PHASE_STRIP = NO; 256 | CURRENT_PROJECT_VERSION = 1; 257 | DEBUG_INFORMATION_FORMAT = dwarf; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | ENABLE_TESTABILITY = YES; 260 | GCC_C_LANGUAGE_STANDARD = gnu99; 261 | GCC_DYNAMIC_NO_PIC = NO; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_OPTIMIZATION_LEVEL = 0; 264 | GCC_PREPROCESSOR_DEFINITIONS = ( 265 | "DEBUG=1", 266 | "$(inherited)", 267 | ); 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 275 | MTL_ENABLE_DEBUG_INFO = YES; 276 | ONLY_ACTIVE_ARCH = YES; 277 | SDKROOT = iphoneos; 278 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 279 | SWIFT_VERSION = 4.0; 280 | TARGETED_DEVICE_FAMILY = "1,2"; 281 | VERSIONING_SYSTEM = "apple-generic"; 282 | VERSION_INFO_PREFIX = ""; 283 | }; 284 | name = Debug; 285 | }; 286 | A079440B1C4B5E5F000ADEFC /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 291 | CLANG_CXX_LIBRARY = "libc++"; 292 | CLANG_ENABLE_MODULES = YES; 293 | CLANG_ENABLE_OBJC_ARC = YES; 294 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 295 | CLANG_WARN_BOOL_CONVERSION = YES; 296 | CLANG_WARN_COMMA = YES; 297 | CLANG_WARN_CONSTANT_CONVERSION = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INFINITE_RECURSION = YES; 302 | CLANG_WARN_INT_CONVERSION = YES; 303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 304 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 305 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 306 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 307 | CLANG_WARN_STRICT_PROTOTYPES = YES; 308 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 309 | CLANG_WARN_UNREACHABLE_CODE = YES; 310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 311 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 312 | COPY_PHASE_STRIP = NO; 313 | CURRENT_PROJECT_VERSION = 1; 314 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 315 | ENABLE_NS_ASSERTIONS = NO; 316 | ENABLE_STRICT_OBJC_MSGSEND = YES; 317 | GCC_C_LANGUAGE_STANDARD = gnu99; 318 | GCC_NO_COMMON_BLOCKS = YES; 319 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 320 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 321 | GCC_WARN_UNDECLARED_SELECTOR = YES; 322 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 323 | GCC_WARN_UNUSED_FUNCTION = YES; 324 | GCC_WARN_UNUSED_VARIABLE = YES; 325 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 326 | MTL_ENABLE_DEBUG_INFO = NO; 327 | SDKROOT = iphoneos; 328 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 329 | SWIFT_VERSION = 4.0; 330 | TARGETED_DEVICE_FAMILY = "1,2"; 331 | VALIDATE_PRODUCT = YES; 332 | VERSIONING_SYSTEM = "apple-generic"; 333 | VERSION_INFO_PREFIX = ""; 334 | }; 335 | name = Release; 336 | }; 337 | A079440D1C4B5E5F000ADEFC /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | CLANG_ENABLE_MODULES = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 342 | DEFINES_MODULE = YES; 343 | DYLIB_COMPATIBILITY_VERSION = 1; 344 | DYLIB_CURRENT_VERSION = 1; 345 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 346 | INFOPLIST_FILE = SimplePDF/Info.plist; 347 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 349 | PRODUCT_BUNDLE_IDENTIFIER = io.github.nrewik.SimplePDF; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | SKIP_INSTALL = YES; 352 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 353 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 354 | SWIFT_VERSION = 4.2; 355 | }; 356 | name = Debug; 357 | }; 358 | A079440E1C4B5E5F000ADEFC /* Release */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | CLANG_ENABLE_MODULES = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 363 | DEFINES_MODULE = YES; 364 | DYLIB_COMPATIBILITY_VERSION = 1; 365 | DYLIB_CURRENT_VERSION = 1; 366 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 367 | INFOPLIST_FILE = SimplePDF/Info.plist; 368 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = io.github.nrewik.SimplePDF; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SKIP_INSTALL = YES; 373 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 374 | SWIFT_VERSION = 4.2; 375 | }; 376 | name = Release; 377 | }; 378 | A07944101C4B5E5F000ADEFC /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | CLANG_ENABLE_MODULES = YES; 382 | INFOPLIST_FILE = SimplePDFTests/Info.plist; 383 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 384 | PRODUCT_BUNDLE_IDENTIFIER = io.github.nrewik.SimplePDFTests; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SWIFT_OBJC_BRIDGING_HEADER = ""; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 388 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 389 | SWIFT_VERSION = 4.0; 390 | }; 391 | name = Debug; 392 | }; 393 | A07944111C4B5E5F000ADEFC /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | CLANG_ENABLE_MODULES = YES; 397 | INFOPLIST_FILE = SimplePDFTests/Info.plist; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 399 | PRODUCT_BUNDLE_IDENTIFIER = io.github.nrewik.SimplePDFTests; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | SWIFT_OBJC_BRIDGING_HEADER = ""; 402 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 403 | SWIFT_VERSION = 4.0; 404 | }; 405 | name = Release; 406 | }; 407 | /* End XCBuildConfiguration section */ 408 | 409 | /* Begin XCConfigurationList section */ 410 | A07943F21C4B5E5F000ADEFC /* Build configuration list for PBXProject "SimplePDF" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | A079440A1C4B5E5F000ADEFC /* Debug */, 414 | A079440B1C4B5E5F000ADEFC /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | A079440C1C4B5E5F000ADEFC /* Build configuration list for PBXNativeTarget "SimplePDF" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | A079440D1C4B5E5F000ADEFC /* Debug */, 423 | A079440E1C4B5E5F000ADEFC /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | A079440F1C4B5E5F000ADEFC /* Build configuration list for PBXNativeTarget "SimplePDFTests" */ = { 429 | isa = XCConfigurationList; 430 | buildConfigurations = ( 431 | A07944101C4B5E5F000ADEFC /* Debug */, 432 | A07944111C4B5E5F000ADEFC /* Release */, 433 | ); 434 | defaultConfigurationIsVisible = 0; 435 | defaultConfigurationName = Release; 436 | }; 437 | /* End XCConfigurationList section */ 438 | }; 439 | rootObject = A07943EF1C4B5E5F000ADEFC /* Project object */; 440 | } 441 | -------------------------------------------------------------------------------- /SimplePDF.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SimplePDF/SimplePDF.h: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePDF.h 3 | // SimplePDF 4 | // 5 | // Created by Nutchaphon Rewik on 17/01/2016. 6 | // Copyright © 2016 Nutchaphon Rewik. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SimplePDF. 12 | FOUNDATION_EXPORT double SimplePDFVersionNumber; 13 | 14 | //! Project version string for SimplePDF. 15 | FOUNDATION_EXPORT const unsigned char SimplePDFVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SimplePDF/SimplePDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePDF.swift 3 | // SimplePDF 4 | // 5 | // Created by Nutchaphon Rewik on 13/01/2016. 6 | // Copyright © 2016 Nutchaphon Rewik. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private enum SimplePDFCommand { 12 | 13 | case addText(text:String, font:UIFont, textColor:UIColor) 14 | case addAttributedText( NSAttributedString ) 15 | case addImage(UIImage) 16 | case addLineSpace(CGFloat) 17 | case addHorizontalSpace(CGFloat) 18 | case addLineSeparator(height: CGFloat) 19 | case addTable(rowCount: Int, columnCount: Int, rowHeight: CGFloat, columnWidth: CGFloat?, tableLineWidth: CGFloat, font: UIFont?, tableDefinition:TableDefinition?, dataArray: Array>) 20 | 21 | case setContentAlignment(ContentAlignment) 22 | case beginNewPage 23 | 24 | case beginHorizontalArrangement 25 | case endHorizontalArrangement 26 | 27 | } 28 | 29 | public enum ContentAlignment { 30 | case left, center, right 31 | } 32 | 33 | public struct TableDefinition { 34 | let alignments: [ContentAlignment] 35 | let columnWidths: [CGFloat] 36 | let fonts:[UIFont] 37 | let textColors:[UIColor] 38 | 39 | public init(alignments: [ContentAlignment], 40 | columnWidths: [CGFloat], 41 | fonts:[UIFont], 42 | textColors:[UIColor]) { 43 | self.alignments = alignments 44 | self.columnWidths = columnWidths 45 | self.fonts = fonts 46 | self.textColors = textColors 47 | } 48 | } 49 | 50 | open class SimplePDF { 51 | 52 | /* States */ 53 | fileprivate var commands: [SimplePDFCommand] = [] 54 | 55 | /* Initialization */ 56 | fileprivate let pageBounds: CGRect 57 | fileprivate let pageMarginLeft: CGFloat 58 | fileprivate let pageMarginTop: CGFloat 59 | fileprivate let pageMarginBottom: CGFloat 60 | fileprivate let pageMarginRight: CGFloat 61 | 62 | public init(pageSize: CGSize, pageMargin: CGFloat = 20.0) { 63 | pageBounds = CGRect(origin: CGPoint.zero, size: pageSize) 64 | self.pageMarginLeft = pageMargin 65 | self.pageMarginTop = pageMargin 66 | self.pageMarginRight = pageMargin 67 | self.pageMarginBottom = pageMargin 68 | } 69 | 70 | public init(pageSize: CGSize, pageMarginLeft: CGFloat, pageMarginTop: CGFloat, pageMarginBottom: CGFloat, pageMarginRight: CGFloat) { 71 | pageBounds = CGRect(origin: CGPoint.zero, size: pageSize) 72 | self.pageMarginBottom = pageMarginBottom 73 | self.pageMarginRight = pageMarginRight 74 | self.pageMarginTop = pageMarginTop 75 | self.pageMarginLeft = pageMarginLeft 76 | } 77 | 78 | 79 | /// Text will be drawn from the current font and alignment settings. 80 | /// 81 | /// If text is too long and doesn't fit in the current page. 82 | /// SimplePDF will begin a new page and draw remaining text. 83 | /// 84 | /// This process will be repeated untill there's no text left to draw. 85 | open func addText(_ text: String, font:UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize), textColor:UIColor = UIColor.black) { 86 | commands += [ .addText(text: text, font: font, textColor: textColor) ] 87 | } 88 | 89 | 90 | /// - Important: Font and Content alignment settings will be ignored. 91 | /// You have to manually add those attributes to attributed text yourself. 92 | open func addAttributedText( _ attributedText: NSAttributedString) { 93 | commands += [ .addAttributedText(attributedText) ] 94 | } 95 | 96 | open func addImage(_ image: UIImage) { 97 | commands += [ .addImage(image) ] 98 | } 99 | 100 | open func addLineSpace(_ space: CGFloat) { 101 | commands += [ .addLineSpace(space) ] 102 | } 103 | 104 | open func addVerticalSpace(_ space:CGFloat) { 105 | commands += [ .addLineSpace(space) ] 106 | } 107 | 108 | open func addHorizontalSpace(_ space: CGFloat) { 109 | commands += [ .addHorizontalSpace(space) ] 110 | } 111 | 112 | open func addLineSeparator(height: CGFloat = 1.0) { 113 | commands += [ .addLineSeparator(height: height) ] 114 | } 115 | 116 | open func addTable(_ rowCount: Int, columnCount: Int, rowHeight: CGFloat, columnWidth: CGFloat, tableLineWidth: CGFloat, font: UIFont, dataArray: Array>) { 117 | commands += [ .addTable(rowCount: rowCount, columnCount: columnCount, rowHeight: rowHeight, columnWidth: columnWidth, tableLineWidth: tableLineWidth, font: font, tableDefinition: nil, dataArray: dataArray) ] 118 | } 119 | 120 | open func addTable(_ rowCount: Int, columnCount: Int, rowHeight: CGFloat, tableLineWidth: CGFloat, tableDefinition: TableDefinition, dataArray: Array>) { 121 | commands += [ .addTable(rowCount: rowCount, columnCount: columnCount, rowHeight: rowHeight, columnWidth: nil, tableLineWidth: tableLineWidth, font: nil, tableDefinition: tableDefinition, dataArray: dataArray) ] 122 | } 123 | 124 | open func setContentAlignment(_ alignment: ContentAlignment) { 125 | commands += [ .setContentAlignment(alignment) ] 126 | } 127 | 128 | open func beginNewPage() { 129 | commands += [ .beginNewPage ] 130 | } 131 | 132 | open func beginHorizontalArrangement() { 133 | commands += [ .beginHorizontalArrangement ] 134 | } 135 | 136 | open func endHorizontalArrangement() { 137 | commands += [ .endHorizontalArrangement ] 138 | } 139 | 140 | /// - returns: drawing text rect 141 | fileprivate func drawText(_ text: String, font: UIFont, textColor: UIColor, alignment: ContentAlignment, currentOffset: CGPoint) -> CGRect { 142 | 143 | // Draw attributed text from font and paragraph style attribute. 144 | 145 | let paragraphStyle = NSMutableParagraphStyle() 146 | switch alignment { 147 | case .left: 148 | paragraphStyle.alignment = .left 149 | case .center: 150 | paragraphStyle.alignment = .center 151 | case .right: 152 | paragraphStyle.alignment = .right 153 | } 154 | 155 | let attributes: [NSAttributedString.Key: Any] = [ 156 | .font: font, 157 | .foregroundColor: textColor, 158 | .paragraphStyle: paragraphStyle 159 | ] 160 | let attributedText = NSAttributedString(string: text, attributes: attributes) 161 | 162 | return drawAttributedText(attributedText, currentOffset: currentOffset) 163 | } 164 | 165 | fileprivate func drawAttributedText( _ attributedText: NSAttributedString, currentOffset: CGPoint) -> CGRect { 166 | 167 | var drawingYoffset = currentOffset.y 168 | 169 | let currentText = CFAttributedStringCreateCopy(nil, attributedText as CFAttributedString) 170 | let framesetter = CTFramesetterCreateWithAttributedString(currentText!) 171 | var currentRange = CFRange(location: 0, length: 0) 172 | var done = false 173 | 174 | var lastDrawnFrame: CGRect! 175 | 176 | repeat { 177 | 178 | // Get the graphics context. 179 | let currentContext = UIGraphicsGetCurrentContext()! 180 | 181 | // Push state 182 | currentContext.saveGState() 183 | 184 | // Put the text matrix into a known state. This ensures 185 | // that no old scaling factors are left in place. 186 | currentContext.textMatrix = CGAffineTransform.identity 187 | 188 | // print("y offset: \t\(drawingYOffset)") 189 | 190 | let textMaxWidth = pageBounds.width - pageMarginLeft - pageMarginRight - currentOffset.x 191 | let textMaxHeight = pageBounds.height - pageMarginBottom - drawingYoffset 192 | 193 | // print("drawing y offset: \t\(drawingYOffset)") 194 | // print("text max height: \t\(textMaxHeight)") 195 | 196 | // Create a path object to enclose the text. 197 | let frameRect = CGRect(x: currentOffset.x, y: drawingYoffset, width: textMaxWidth, height: textMaxHeight) 198 | let framePath = UIBezierPath(rect: frameRect).cgPath 199 | 200 | // Get the frame that will do the rendering. 201 | // The currentRange variable specifies only the starting point. The framesetter 202 | // lays out as much text as will fit into the frame. 203 | let frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil) 204 | 205 | // Core Text draws from the bottom-left corner up, so flip 206 | // the current transform prior to drawing. 207 | currentContext.translateBy(x: 0, y: pageBounds.height + drawingYoffset - pageMarginBottom) 208 | currentContext.scaleBy(x: 1.0, y: -1.0) 209 | 210 | // Draw the frame. 211 | CTFrameDraw(frameRef, currentContext) 212 | 213 | // Pop state 214 | currentContext.restoreGState() 215 | 216 | // Update the current range based on what was drawn. 217 | let visibleRange = CTFrameGetVisibleStringRange(frameRef) 218 | currentRange = CFRange(location: visibleRange.location + visibleRange.length , length: 0) 219 | 220 | // Update last drawn frame 221 | let constraintSize = CGSize(width: textMaxWidth, height: textMaxHeight) 222 | let drawnSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, visibleRange, nil, constraintSize, nil) 223 | lastDrawnFrame = CGRect(x: currentOffset.x, y: drawingYoffset, width: drawnSize.width, height: drawnSize.height) 224 | 225 | // print(suggestionSize) 226 | 227 | // If we're at the end of the text, exit the loop. 228 | // print("\(currentRange.location) \(CFAttributedStringGetLength(currentText))") 229 | if currentRange.location == CFAttributedStringGetLength(currentText) { 230 | done = true 231 | // print("exit") 232 | } else { 233 | // begin a new page to draw text that is remaining. 234 | UIGraphicsBeginPDFPageWithInfo(pageBounds, nil) 235 | drawingYoffset = pageMarginTop 236 | // print("begin a new page to draw text that is remaining") 237 | } 238 | 239 | 240 | } while(!done) 241 | 242 | return lastDrawnFrame 243 | } 244 | 245 | /// - returns: drawing image rect 246 | fileprivate func drawImage(_ image: UIImage, alignment: ContentAlignment, currentOffset: CGPoint) -> CGRect { 247 | 248 | /* calculate the aspect size of image */ 249 | 250 | let maxWidth = min( image.size.width, pageBounds.width ) 251 | let maxHeight = min( image.size.height, pageBounds.height - currentOffset.y ) 252 | 253 | let wFactor = image.size.width / maxWidth 254 | let hFactor = image.size.height / maxHeight 255 | 256 | let factor = max(wFactor, hFactor) 257 | 258 | let aspectWidth = image.size.width / factor 259 | let aspectHeight = image.size.height / factor 260 | 261 | /* calculate x offset for rendering */ 262 | let renderingXoffset: CGFloat 263 | switch alignment { 264 | case .left: 265 | renderingXoffset = currentOffset.x 266 | case .center: 267 | renderingXoffset = ( pageBounds.width - currentOffset.x - aspectWidth ) / 2.0 268 | case .right: 269 | let right = pageBounds.width - pageMarginRight 270 | renderingXoffset = right - aspectWidth 271 | } 272 | 273 | let renderingRect = CGRect(x: renderingXoffset, y: currentOffset.y, width: aspectWidth, height: aspectHeight) 274 | 275 | // render image to current pdf context 276 | image.draw(in: renderingRect) 277 | 278 | return renderingRect 279 | } 280 | 281 | fileprivate func drawLineSeparator(height: CGFloat, currentOffset: CGPoint) -> CGRect { 282 | 283 | let drawRect = CGRect(x: currentOffset.x, y: currentOffset.y, width: pageBounds.width - pageMarginLeft - pageMarginRight, height: height) 284 | let path = UIBezierPath(rect: drawRect).cgPath 285 | 286 | // Get the graphics context. 287 | let currentContext = UIGraphicsGetCurrentContext()! 288 | 289 | // Set color 290 | UIColor.black.setStroke() 291 | UIColor.black.setFill() 292 | 293 | // Draw path 294 | currentContext.addPath(path) 295 | currentContext.drawPath(using: .fillStroke) 296 | 297 | // print(drawRect) 298 | 299 | return drawRect 300 | } 301 | 302 | fileprivate func drawTable(rowCount: Int, alignment: ContentAlignment, columnCount: Int, rowHeight: CGFloat, columnWidth: CGFloat?, tableLineWidth: CGFloat, font: UIFont?, tableDefinition:TableDefinition?, dataArray: Array>, currentOffset: CGPoint) -> CGRect { 303 | 304 | let height = (CGFloat(rowCount)*rowHeight) 305 | 306 | let drawRect = CGRect(x: currentOffset.x, y: currentOffset.y, width: pageBounds.width - pageMarginLeft - pageMarginRight, height: height) 307 | 308 | UIColor.black.setStroke() 309 | UIColor.black.setFill() 310 | 311 | let tableWidth = { () -> CGFloat in 312 | if let cws = tableDefinition?.columnWidths { 313 | return cws.reduce(0, { (result, current) -> CGFloat in 314 | return result + current 315 | }) 316 | } else if let cw = columnWidth { 317 | return CGFloat(columnCount) * cw 318 | } 319 | 320 | return 0 // default which should never be use, because either columnWidth, or columnsWidths is set 321 | }() 322 | 323 | for i in 0...rowCount { 324 | let newOrigin = drawRect.origin.y + rowHeight*CGFloat(i) 325 | 326 | 327 | 328 | let from = CGPoint(x: drawRect.origin.x, y: newOrigin) 329 | let to = CGPoint(x: drawRect.origin.x + tableWidth, y: newOrigin) 330 | 331 | drawLineFromPoint(from, to: to, lineWidth: tableLineWidth) 332 | } 333 | 334 | for i in 0...columnCount { 335 | let currentOffset = { () -> CGFloat in 336 | if let cws = tableDefinition?.columnWidths { 337 | var offset:CGFloat = 0 338 | for x in 0.. CGFloat in 360 | if let cws = tableDefinition?.columnWidths { 361 | var offset:CGFloat = 0 362 | for x in 0.. UIFont in 377 | if let f = tableDefinition?.fonts { 378 | if (f.count > j){ 379 | return f[j] 380 | } 381 | } else if let f = font { 382 | return f 383 | } 384 | 385 | return UIFont.systemFont(ofSize: UIFont.systemFontSize) 386 | }() 387 | 388 | let currentTextColor = { () -> UIColor in 389 | if let t = tableDefinition?.textColors { 390 | if t.count > j { 391 | return t[j] 392 | } 393 | } 394 | 395 | return UIColor.black 396 | }() 397 | 398 | let currentColumnWidth = { () -> CGFloat in 399 | if let cw = tableDefinition?.columnWidths { 400 | if cw.count > j { 401 | return cw[j] 402 | } 403 | } else if let cw = columnWidth { 404 | return cw 405 | } 406 | 407 | return 100 // default which should never be use, because either columnWidth, or columnsWidths is set 408 | }() 409 | 410 | let frame = CGRect(x: newOriginX, y: newOriginY, width: currentColumnWidth, height: rowHeight) 411 | drawTextInCell(frame, text: dataArray[i][j] as NSString, alignment: alignment, font: currentFont, textColor: currentTextColor) 412 | } 413 | } 414 | 415 | return drawRect 416 | } 417 | 418 | fileprivate func drawLineFromPoint(_ from: CGPoint, to: CGPoint, lineWidth: CGFloat) { 419 | let context = UIGraphicsGetCurrentContext()! 420 | context.setLineWidth(lineWidth) 421 | let colorspace = CGColorSpaceCreateDeviceRGB() 422 | let color = CGColor(colorSpace: colorspace, components: [0.2, 0.2, 0.2, 1.0]) 423 | 424 | context.setStrokeColor(color!) 425 | context.move(to: CGPoint(x: from.x, y: from.y)) 426 | context.addLine(to: CGPoint(x: to.x, y: to.y)) 427 | 428 | context.strokePath() 429 | } 430 | 431 | fileprivate func drawTextInCell(_ rect: CGRect, text: NSString, alignment: ContentAlignment, font: UIFont, textColor:UIColor) { 432 | let paraStyle = NSMutableParagraphStyle() 433 | 434 | let skew = 0.0 435 | 436 | let attributes: [NSAttributedString.Key: Any] = [ 437 | .foregroundColor: textColor, 438 | .paragraphStyle: paraStyle, 439 | .obliqueness: skew, 440 | .font: font 441 | ] 442 | 443 | let size = text.size(withAttributes: attributes) 444 | 445 | let x:CGFloat = { () -> CGFloat in 446 | switch alignment { 447 | case .left: 448 | return 0 449 | case .center: 450 | return (rect.size.width - size.width)/2 451 | case .right: 452 | return rect.size.width - size.width 453 | } 454 | }() 455 | let y = (rect.size.height - size.height)/2 456 | 457 | text.draw(at: CGPoint(x: rect.origin.x + x, y: rect.origin.y + y), withAttributes: attributes) 458 | } 459 | 460 | enum ArrangementDirection { 461 | case horizontal 462 | case vertical 463 | } 464 | 465 | open func generatePDFdata() -> Data { 466 | 467 | let pdfData = NSMutableData() 468 | 469 | UIGraphicsBeginPDFContextToData(pdfData, pageBounds, nil) 470 | UIGraphicsBeginPDFPageWithInfo(pageBounds, nil) 471 | 472 | var currentOffset = CGPoint(x: pageMarginLeft, y: pageMarginTop) 473 | var alignment = ContentAlignment.left 474 | var arrangementDirection = ArrangementDirection.vertical 475 | var lastYOffset = currentOffset.y 476 | 477 | for command in commands { 478 | 479 | switch command{ 480 | case let .addText(text, font, textColor): 481 | let textFrame = drawText(text, font: font, textColor: textColor, alignment: alignment, currentOffset: currentOffset) 482 | lastYOffset = textFrame.origin.y + textFrame.height 483 | switch arrangementDirection { 484 | case .horizontal: 485 | currentOffset = CGPoint(x: textFrame.origin.x + textFrame.width, y: currentOffset.y) 486 | case .vertical: 487 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 488 | } 489 | 490 | case let .addAttributedText(attributedText): 491 | let textFrame = drawAttributedText(attributedText, currentOffset: currentOffset) 492 | lastYOffset = textFrame.origin.y + textFrame.height 493 | switch arrangementDirection { 494 | case .horizontal: 495 | currentOffset = CGPoint(x: textFrame.origin.x + textFrame.width, y: currentOffset.y) 496 | case .vertical: 497 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 498 | } 499 | 500 | case let .addImage(image): 501 | let imageFrame = drawImage(image, alignment: alignment, currentOffset: currentOffset) 502 | lastYOffset = imageFrame.origin.y + imageFrame.height 503 | switch arrangementDirection { 504 | case .horizontal: 505 | currentOffset = CGPoint(x: imageFrame.origin.x + imageFrame.width, y: currentOffset.y) 506 | case .vertical: 507 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 508 | } 509 | 510 | case let .addLineSeparator(height: height): 511 | let drawRect = drawLineSeparator(height: height, currentOffset: currentOffset) 512 | lastYOffset = drawRect.origin.y + drawRect.height 513 | switch arrangementDirection { 514 | case .horizontal: 515 | currentOffset = CGPoint(x: drawRect.origin.x + drawRect.width, y: currentOffset.y) 516 | case .vertical: 517 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 518 | } 519 | 520 | case let .addLineSpace(space): 521 | lastYOffset = currentOffset.y + space 522 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 523 | 524 | case let .addHorizontalSpace(space): 525 | lastYOffset = currentOffset.y 526 | currentOffset = CGPoint(x: currentOffset.x + space, y: currentOffset.y) 527 | 528 | case let .addTable(rowCount, columnCount, rowHeight, columnWidth, tableLineWidth, font, tableDefinition, dataArray): 529 | let tableFrame = drawTable(rowCount: rowCount, alignment: alignment, columnCount: columnCount, rowHeight: rowHeight, columnWidth: columnWidth, tableLineWidth: tableLineWidth, font: font, tableDefinition: tableDefinition, dataArray: dataArray, currentOffset: currentOffset) 530 | lastYOffset = tableFrame.origin.y + tableFrame.height 531 | switch arrangementDirection { 532 | case .horizontal: 533 | currentOffset = CGPoint(x: tableFrame.origin.x + tableFrame.width, y: currentOffset.y) 534 | case .vertical: 535 | currentOffset = CGPoint(x: currentOffset.x, y: lastYOffset) 536 | } 537 | 538 | case let .setContentAlignment(newAlignment): 539 | alignment = newAlignment 540 | 541 | case .beginNewPage: 542 | UIGraphicsBeginPDFPageWithInfo(pageBounds, nil) 543 | currentOffset = CGPoint(x: pageMarginLeft, y: pageMarginTop) 544 | lastYOffset = currentOffset.y 545 | 546 | case .beginHorizontalArrangement: 547 | arrangementDirection = .horizontal 548 | 549 | case .endHorizontalArrangement: 550 | arrangementDirection = .vertical 551 | currentOffset = CGPoint(x: pageMarginLeft, y: lastYOffset) 552 | } 553 | } 554 | 555 | UIGraphicsEndPDFContext() 556 | 557 | return pdfData as Data 558 | } 559 | 560 | } 561 | -------------------------------------------------------------------------------- /SimplePDFTests/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 | -------------------------------------------------------------------------------- /SimplePDFTests/SimplePDFTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePDFTests.swift 3 | // SimplePDFTests 4 | // 5 | // Created by Nutchaphon Rewik on 17/01/2016. 6 | // Copyright © 2016 Nutchaphon Rewik. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SimplePDF 11 | 12 | class SimplePDFTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | 28 | let a4PaperSize = CGSize(width: 595, height: 842) 29 | let pdf = SimplePDF(pageSize: a4PaperSize) 30 | 31 | // load test image 32 | let bundle = Bundle(for: type(of: self)) 33 | let path = bundle.path(forResource: "simple_pdf_logo", ofType: "png")! 34 | let image = UIImage(contentsOfFile: path)! 35 | 36 | pdf.setContentAlignment(.center) 37 | pdf.addImage(image) 38 | 39 | pdf.addLineSpace(30) 40 | 41 | pdf.setContentAlignment(.left) 42 | pdf.addText("Normal text follows by line separator") 43 | pdf.addLineSeparator() 44 | 45 | pdf.addLineSpace(20.0) 46 | 47 | pdf.setContentAlignment(.right) 48 | pdf.addText("Text after set content alignment to .Right") 49 | pdf.addLineSpace(20.0) 50 | 51 | pdf.addText("Cras quis eros orci.\nLorem ipsum dolor sit amet, consectetur adipiscing elit.\nDonec mollis vitae mi ut lobortis.\nUt ultrices mi vel neque venenatis, ut efficitur metus eleifend. Sed pellentesque lobortis est quis maximus. Maecenas ultricies risus et enim consectetur, id lobortis ante porta. Quisque at euismod enim. Vestibulum faucibus purus non justo fringilla, sit amet iaculis ex pellentesque. Vestibulum eget vulputate diam, sit amet ornare sem. Duis at eros non tortor malesuada accumsan.\nNunc vel libero ut sapien dictum iaculis a vel odio. Quisque purus turpis, tristique auctor ex non, consectetur scelerisque lorem. Mauris est justo, sollicitudin sit amet nisi a, mattis posuere orci. Sed elementum est at est tristique gravida. Aliquam iaculis, metus facilisis varius viverra, nunc libero ultricies arcu, in accumsan sem nibh vel purus.") 52 | 53 | pdf.addLineSpace(30) 54 | 55 | pdf.setContentAlignment(.center) 56 | 57 | pdf.addText("Center Text") 58 | pdf.addLineSpace(20.0) 59 | pdf.addText("Cras varius leo ac lectus malesuada, ut rhoncus nunc blandit.\n In venenatis diam et vehicula suscipit.\n Aliquam in ante at dolor sodales consectetur ut semper quam.\n Vivamus massa purus, congue sed leo sed, lobortis consectetur lacus. Nunc sed tortor nec augue mattis faucibus. Sed malesuada metus in sapien efficitur, ut aliquet nisl lobortis. Vestibulum faucibus purus non justo fringilla, sit amet iaculis ex pellentesque. Vestibulum eget vulputate diam, sit amet ornare sem. Aliquam erat volutpat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin scelerisque posuere mi, non consequat mauris auctor a. Fusce lacinia auctor lectus a elementum.") 60 | 61 | 62 | pdf.addLineSpace(30.0) 63 | 64 | pdf.setContentAlignment(.left) 65 | let textString = "This is an example of long text. If the text doesn't fit in the current page. Simple pdf will draw a part of text, and automatically begin a new page to draw the remaining text. This process will be repeated until there's no text left to draw. " 66 | pdf.addText(textString) 67 | 68 | 69 | pdf.beginNewPage() 70 | pdf.addText("Begin new page") 71 | 72 | if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first { 73 | 74 | let fileName = "example.pdf" 75 | let documentsFileName = documentDirectories + "/" + fileName 76 | 77 | let pdfData = pdf.generatePDFdata() 78 | do{ 79 | try pdfData.write(to: URL(fileURLWithPath: documentsFileName), options: .atomic) 80 | print("\nThe generated pdf can be found at:") 81 | print("\n\t\(documentsFileName)\n") 82 | }catch{ 83 | print(error) 84 | } 85 | } 86 | 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /SimplePDFTests/simple_pdf_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nRewik/SimplePDF/35e3e7ea6500eaf9244d66b9e6d316f7b85072aa/SimplePDFTests/simple_pdf_logo.png --------------------------------------------------------------------------------