(_ view: T, scaleFactor: CGFloat, area: CGRect? = nil, completion: (T) -> Void = { _ in }) throws {
39 | guard scaleFactor > 0.0 else {
40 | throw PDFGenerateError.invalidScaleFactor
41 | }
42 |
43 | let size: CGSize
44 | let origin: CGPoint
45 | if let area = area {
46 | origin = area.origin
47 | size = area.size
48 | } else {
49 | origin = .zero
50 | size = getPageSize()
51 | }
52 |
53 | guard size.width > 0 && size.height > 0 else {
54 | throw PDFGenerateError.zeroSizeView(self)
55 | }
56 | guard let context = UIGraphicsGetCurrentContext() else {
57 | throw PDFGenerateError.invalidContext
58 | }
59 |
60 | let renderFrame = CGRect(origin: CGPoint(x: origin.x * scaleFactor, y: origin.y * scaleFactor),
61 | size: CGSize(width: size.width * scaleFactor, height: size.height * scaleFactor))
62 | autoreleasepool {
63 | let superView = view.superview
64 | view.removeFromSuperview()
65 | UIGraphicsBeginPDFPageWithInfo(CGRect(origin: .zero, size: renderFrame.size), nil)
66 | context.translateBy(x: -renderFrame.origin.x, y: -renderFrame.origin.y)
67 | view.layer.render(in: context)
68 | superView?.addSubview(view)
69 | superView?.layoutIfNeeded()
70 | completion(view)
71 | }
72 | }
73 |
74 | func renderPDFPage(scaleFactor: CGFloat) throws {
75 | try self.renderPDFPage(scaleFactor: scaleFactor, area: nil)
76 | }
77 |
78 | func renderPDFPage(scaleFactor: CGFloat, area: CGRect?) throws {
79 | func renderScrollView(_ scrollView: UIScrollView, area: CGRect?) throws {
80 | let tmp = scrollView.tempInfo
81 | scrollView.transformForRender()
82 | try _render(scrollView, scaleFactor: scaleFactor, area: area) { scrollView in
83 | scrollView.restore(tmp)
84 | }
85 | }
86 |
87 | if let webView = self as? WKWebView {
88 | try renderScrollView(webView.scrollView, area: area)
89 | } else if let webView = self as? WKWebView {
90 | try renderScrollView(webView.scrollView, area: area)
91 | } else if let scrollView = self as? UIScrollView {
92 | try renderScrollView(scrollView, area: area)
93 | } else {
94 | try _render(self, scaleFactor: scaleFactor, area: area)
95 | }
96 | }
97 |
98 | fileprivate func getPageSize() -> CGSize {
99 | switch self {
100 | case (let webView as WKWebView):
101 | return webView.scrollView.contentSize
102 | case (let scrollView as UIScrollView):
103 | return scrollView.contentSize
104 | default:
105 | return self.frame.size
106 | }
107 | }
108 | }
109 |
110 | extension UIImage: PDFPageRenderable {
111 | func renderPDFPage(scaleFactor: CGFloat) throws {
112 | guard scaleFactor > 0.0 else {
113 | throw PDFGenerateError.invalidScaleFactor
114 | }
115 | autoreleasepool {
116 | let bounds = CGRect(
117 | origin: .zero,
118 | size: CGSize(
119 | width: size.width * scaleFactor,
120 | height: size.height * scaleFactor
121 | )
122 | )
123 | UIGraphicsBeginPDFPageWithInfo(bounds, nil)
124 | draw(in: bounds)
125 | }
126 | }
127 | }
128 |
129 | protocol UIImageConvertible {
130 | func asUIImage() throws -> UIImage
131 | }
132 |
133 | extension UIImage: UIImageConvertible {
134 | func asUIImage() throws -> UIImage {
135 | return self
136 | }
137 | }
138 |
139 | extension String: UIImageConvertible {
140 | func asUIImage() throws -> UIImage {
141 | guard let image = UIImage(contentsOfFile: self) else {
142 | throw PDFGenerateError.imageLoadFailed(self)
143 | }
144 | return image
145 | }
146 | }
147 |
148 | extension Data: UIImageConvertible {
149 | func asUIImage() throws -> UIImage {
150 | guard let image = UIImage(data: self) else {
151 | throw PDFGenerateError.imageLoadFailed(self)
152 | }
153 | return image
154 | }
155 | }
156 |
157 | extension CGImage: UIImageConvertible {
158 | func asUIImage() throws -> UIImage {
159 | return UIImage(cgImage: self)
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/PDFGenerator/PDFPagedScrollViewConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDFPagedScrollViewConfiguration.swift
3 | // ActionSheetPicker-3.0
4 | //
5 | // Created by Philip Messlehner on 03.08.18.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct PDFPagedScrollViewConfiguration {
11 | public let overlapPercentage: CGFloat
12 | public let ratio: PageRatio
13 |
14 | public init(overlapPercentage: CGFloat, ratio: PageRatio) {
15 | self.overlapPercentage = overlapPercentage
16 | self.ratio = ratio
17 | }
18 |
19 | public enum PageRatio {
20 | case dinA3, dinA4, dinA5
21 | case ansiA, ansiB, ansiC
22 | case invoice, executive, legal, letter
23 | case custom(CGFloat)
24 |
25 | public var rawValue: CGFloat {
26 | switch self {
27 | case .dinA3: return 297.0 / 420.0
28 | case .dinA4: return 210.0 / 297.0
29 | case .dinA5: return 148.0 / 210.0
30 | case .ansiA: return 216.0 / 279.0
31 | case .ansiB: return 279.0 / 432.0
32 | case .ansiC: return 432.0 / 559.0
33 | case .invoice: return 140.0 / 216.0
34 | case .executive: return 184.0 / 267.0
35 | case .legal: return 216.0 / 356.0
36 | case .letter: return PageRatio.ansiA.rawValue
37 | case .custom(let ratio): return ratio
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/PDFGenerator/PDFPassword.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDFPassword.swift
3 | // PDFGenerator
4 | //
5 | // Created by Suguru Kishimoto on 2016/07/08.
6 | //
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public struct PDFPassword {
13 | static let NoPassword = ""
14 | fileprivate static let PasswordLengthMax = 32
15 | let userPassword: String
16 | let ownerPassword: String
17 |
18 | public init(user userPassword: String, owner ownerPassword: String) {
19 | self.userPassword = userPassword
20 | self.ownerPassword = ownerPassword
21 | }
22 |
23 | public init(_ password: String) {
24 | self.init(user: password, owner: password)
25 | }
26 |
27 | func toDocumentInfo() -> [AnyHashable: Any] {
28 | var info: [AnyHashable: Any] = [:]
29 | if userPassword != type(of: self).NoPassword {
30 | info[String(kCGPDFContextUserPassword)] = userPassword
31 | }
32 | if ownerPassword != type(of: self).NoPassword {
33 | info[String(kCGPDFContextOwnerPassword)] = ownerPassword
34 | }
35 | return info
36 | }
37 |
38 | func verify() throws {
39 | guard userPassword.canBeConverted(to: String.Encoding.ascii) else {
40 | throw PDFGenerateError.invalidPassword(userPassword)
41 | }
42 | guard userPassword.count <= type(of: self).PasswordLengthMax else {
43 | throw PDFGenerateError.tooLongPassword(userPassword.count)
44 | }
45 |
46 | guard ownerPassword.canBeConverted(to: String.Encoding.ascii) else {
47 | throw PDFGenerateError.invalidPassword(ownerPassword)
48 | }
49 | guard ownerPassword.count <= type(of: self).PasswordLengthMax else {
50 | throw PDFGenerateError.tooLongPassword(ownerPassword.count)
51 | }
52 | }
53 | }
54 |
55 | extension PDFPassword: ExpressibleByStringLiteral {
56 | public init(unicodeScalarLiteral value: String) {
57 | self.init(value)
58 | }
59 |
60 | public init(extendedGraphemeClusterLiteral value: String) {
61 | self.init(value)
62 | }
63 |
64 | public init(stringLiteral value: String) {
65 | self.init(value)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/PDFGeneratorTests/DPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DPITests.swift
3 | // PDFGenerator
4 | //
5 | // Created by Suguru Kishimoto on 7/23/16.
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import PDFGenerator
11 |
12 | class DPITests: XCTestCase {
13 | func test() {
14 | XCTAssertEqual(DPIType.default.value, 72.0)
15 | XCTAssertEqual(DPIType.dpi_300.value, 300.0)
16 | XCTAssertEqual(DPIType.custom(100.0).value, 100.0)
17 | XCTAssertEqual(DPIType.custom(-100.0).value, DPIType.default.value)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PDFGeneratorTests/FilePathConvertibleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FilePathConvertibleTests.swift
3 | // PDFGenerator
4 | //
5 | // Created by Suguru Kishimoto on 7/23/16.
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import PDFGenerator
11 |
12 | class FilePathConvertibleTests: XCTestCase {
13 |
14 | func test() {
15 | let p1: FilePathConvertible = ""
16 | XCTAssertNotNil(p1.url)
17 | XCTAssertEqual(p1.path, "")
18 | XCTAssertEqual(p1.url, URL(fileURLWithPath: ""))
19 |
20 | let p2: FilePathConvertible = "path/to/hoge.txt"
21 | XCTAssertNotNil(p2.url)
22 | XCTAssertEqual(p2.url, URL(fileURLWithPath: "path/to/hoge.txt"))
23 | XCTAssertEqual(p2.path, "path/to/hoge.txt")
24 |
25 | let p3: FilePathConvertible = URL(fileURLWithPath: "path/to/hoge.txt")
26 | XCTAssertNotNil(p3.url)
27 | XCTAssertEqual(p3.url, URL(fileURLWithPath: "path/to/hoge.txt"))
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PDFGeneratorTests/Images/test_image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgr-ksmt/PDFGenerator/4036333e83a0a2abb23837cc264b2fc8befb0460/PDFGeneratorTests/Images/test_image1.png
--------------------------------------------------------------------------------
/PDFGeneratorTests/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 |
--------------------------------------------------------------------------------
/PDFGeneratorTests/PDFGeneratorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDFGeneratorTests.swift
3 | // PDFGeneratorTests
4 | //
5 | // Created by Suguru Kishimoto on 2016/02/04.
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import PDFGenerator
11 |
12 | class Mock {
13 | struct ImageName {
14 | static let testImage1 = "test_image1"
15 | }
16 |
17 | class func view(_ size: CGSize) -> UIView {
18 | return UIView(frame: CGRect(origin: CGPoint.zero, size: size))
19 | }
20 |
21 | class func scrollView(_ size: CGSize) -> UIScrollView {
22 | return { () -> UIScrollView in
23 | let v = UIScrollView(frame: CGRect(origin: CGPoint.zero, size: size))
24 | v.contentSize = v.frame.size
25 | return v
26 | }()
27 | }
28 |
29 | class func imagePath(_ name: String) -> String {
30 | return Bundle(for: self).path(forResource: name, ofType: "png")!
31 | }
32 |
33 | class func image(_ name: String) -> UIImage {
34 | return UIImage(contentsOfFile: imagePath(name))!
35 | }
36 |
37 | }
38 |
39 | class PDFGeneratorTests: XCTestCase {
40 |
41 | func isExistPDF(_ path: String) -> Bool {
42 | return FileManager.default.fileExists(atPath: path)
43 | }
44 |
45 | func PDFDirectoryPath() -> String {
46 | return NSHomeDirectory() + "/test/"
47 | }
48 |
49 | func PDFfilePath(_ fileName: String) -> String {
50 | return PDFDirectoryPath() + "/\(fileName)"
51 | }
52 |
53 | override func setUp() {
54 | super.setUp()
55 | try! FileManager.default.createDirectory(
56 | atPath: PDFDirectoryPath(),
57 | withIntermediateDirectories: true,
58 | attributes: nil
59 | )
60 | }
61 |
62 | override func tearDown() {
63 | _ = try? FileManager.default.removeItem(atPath: PDFDirectoryPath())
64 | super.tearDown()
65 | }
66 |
67 | // MARK: UIView -> PDF
68 | func testViewToPDF() {
69 | let view = Mock.view(CGSize(width: 100, height: 100))
70 | let view2 = Mock.scrollView(CGSize(width: 100, height: 100))
71 |
72 | let path1 = PDFfilePath("test_sample1.pdf")
73 | _ = try? PDFGenerator.generate(view, to: path1)
74 | XCTAssertTrue(isExistPDF(path1))
75 |
76 | let path2 = PDFfilePath("hoge/test_sample2.pdf")
77 | _ = try? PDFGenerator.generate(view, to: path2)
78 | XCTAssertFalse(isExistPDF(path2))
79 |
80 | let path3 = PDFfilePath("test_sample3.pdf")
81 | _ = try? PDFGenerator.generate(view, to: path3)
82 | XCTAssertTrue(isExistPDF(path3))
83 |
84 | XCTAssertNotNil(try? PDFGenerator.generated(by: view))
85 | XCTAssertNotNil(try? PDFGenerator.generated(by: [view]))
86 | XCTAssertNotNil(try? PDFGenerator.generated(by: [view, view2]))
87 | }
88 |
89 | // MARK: UIImage -> PDF
90 | func testImageToPDF() {
91 | let image1 = Mock.image("test_image1")
92 | let image2 = Mock.image("test_image1")
93 |
94 | let path1 = PDFfilePath("test_sample1.pdf")
95 | _ = try? PDFGenerator.generate(image1, to: path1)
96 | XCTAssertTrue(isExistPDF(path1))
97 |
98 | let path2 = PDFfilePath("hoge/test_sample2.pdf")
99 | _ = try? PDFGenerator.generate(image1, to: path2)
100 | XCTAssertFalse(isExistPDF(path2))
101 |
102 | let path3 = PDFfilePath("test_sample3.pdf")
103 | _ = try? PDFGenerator.generate([image1, image2], to: path3)
104 | XCTAssertTrue(isExistPDF(path3))
105 |
106 | XCTAssertNotNil(try? PDFGenerator.generated(by: image1))
107 | XCTAssertNotNil(try? PDFGenerator.generated(by: [image1]))
108 | XCTAssertNotNil(try? PDFGenerator.generated(by: [image1, image2]))
109 | }
110 |
111 | // MARK: ImagePath(String) -> PDF
112 | func testImagePathToPDF() {
113 | let image1 = Mock.imagePath("test_image1")
114 | let image2 = Mock.imagePath("test_image1")
115 |
116 | let path1 = PDFfilePath("test_sample1.pdf")
117 | _ = try? PDFGenerator.generate(image1, to: path1)
118 | XCTAssertTrue(isExistPDF(path1))
119 |
120 | let path2 = PDFfilePath("hoge/test_sample2.pdf")
121 | _ = try? PDFGenerator.generate(image1, to: path2)
122 | XCTAssertFalse(isExistPDF(path2))
123 |
124 | let path3 = PDFfilePath("test_sample3.pdf")
125 | _ = try? PDFGenerator.generate([image1, image2], to: path3)
126 | XCTAssertTrue(isExistPDF(path3))
127 |
128 | XCTAssertNotNil(try? PDFGenerator.generated(by: image1))
129 | XCTAssertNotNil(try? PDFGenerator.generated(by: [image1]))
130 | XCTAssertNotNil(try? PDFGenerator.generated(by: [image1, image2]))
131 | }
132 |
133 | // MARK: PDFPage -> PDF
134 | func testMixedPageToPDF() {
135 | let p1 = PDFPage.view(Mock.view(CGSize(width: 100, height: 100)))
136 | let p2 = PDFPage.image(Mock.image(Mock.ImageName.testImage1))
137 | let p3 = PDFPage.imagePath(Mock.imagePath(Mock.ImageName.testImage1))
138 | let p4 = PDFPage.whitePage(CGSize(width: 100, height: 100))
139 | let p5 = PDFPage.imageRef(Mock.image(Mock.ImageName.testImage1).cgImage!)
140 | let p6 = PDFPage.binary(Mock.image(Mock.ImageName.testImage1).pngData()!)
141 |
142 | let path1 = PDFfilePath("test_sample1.pdf")
143 | _ = try? PDFGenerator.generate(p1, to: path1)
144 | XCTAssertTrue(isExistPDF(path1))
145 |
146 | let path2 = PDFfilePath("hoge/test_sample2.pdf")
147 | _ = try? PDFGenerator.generate(p2, to: path2)
148 | XCTAssertFalse(isExistPDF(path2))
149 |
150 | let path3 = PDFfilePath("test_sample3.pdf")
151 | _ = try? PDFGenerator.generate([p1, p2, p3, p4], to: path3)
152 | XCTAssertTrue(isExistPDF(path3))
153 |
154 | XCTAssertNotNil(try? PDFGenerator.generated(by: p1))
155 | XCTAssertNotNil(try? PDFGenerator.generated(by: [p2]))
156 | XCTAssertNotNil(try? PDFGenerator.generated(by: [p3, p4]))
157 | XCTAssertNotNil(try? PDFGenerator.generated(by: [p5, p6]))
158 |
159 | }
160 |
161 | // swiftlint:disable function_body_length
162 | func testErrors() {
163 | let view = Mock.view(CGSize(width: 100, height: 100))
164 | let image = Mock.image(Mock.ImageName.testImage1)
165 | let imagePath = Mock.imagePath(Mock.ImageName.testImage1)
166 | let viewPage = PDFPage.view(Mock.view(CGSize(width: 100, height: 100)))
167 | let imagePage = PDFPage.image(Mock.image(Mock.ImageName.testImage1))
168 | let imagePathPage = PDFPage.imagePath(Mock.imagePath(Mock.ImageName.testImage1))
169 | let whitePage = PDFPage.whitePage(CGSize(width: 100, height: 100))
170 | let views = [
171 | Mock.view(CGSize(width: 100, height: 100)),
172 | Mock.view(CGSize(width: 100, height: 100))
173 | ]
174 | let images = [
175 | Mock.image(Mock.ImageName.testImage1),
176 | Mock.image(Mock.ImageName.testImage1)
177 | ]
178 | let imagePaths = [
179 | Mock.imagePath(Mock.ImageName.testImage1),
180 | Mock.imagePath(Mock.ImageName.testImage1)
181 | ]
182 |
183 | let pages = [
184 | PDFPage.view(Mock.view(CGSize(width: 100, height: 100))),
185 | PDFPage.image(Mock.image(Mock.ImageName.testImage1)),
186 | PDFPage.imagePath(Mock.imagePath(Mock.ImageName.testImage1)),
187 | PDFPage.whitePage(CGSize(width: 100, height: 100))
188 | ]
189 |
190 | let mocks: [Any] = [
191 | view,
192 | image,
193 | imagePath,
194 | viewPage,
195 | imagePage,
196 | imagePathPage,
197 | whitePage,
198 | views,
199 | images,
200 | imagePaths,
201 | pages
202 | ]
203 |
204 | let emptyMocks: [Any] = [
205 | [UIView](),
206 | [UIImage](),
207 | [String](),
208 | [PDFPage]()
209 | ]
210 |
211 | // MARK: check EmptyOutputPath
212 | mocks.forEach {
213 | do {
214 | if let page = $0 as? UIView {
215 | try PDFGenerator.generate(page, to: "")
216 | } else if let page = $0 as? UIImage {
217 | try PDFGenerator.generate(page, to: "")
218 | } else if let page = $0 as? String {
219 | try PDFGenerator.generate(page, to: "")
220 | } else if let page = $0 as? PDFPage {
221 | try PDFGenerator.generate(page, to: "")
222 | } else if let pages = $0 as? [UIView] {
223 | try PDFGenerator.generate(pages, to: "")
224 | } else if let pages = $0 as? [UIImage] {
225 | try PDFGenerator.generate(pages, to: "")
226 | } else if let pages = $0 as? [String] {
227 | try PDFGenerator.generate(pages, to: "")
228 | } else if let pages = $0 as? [PDFPage] {
229 | try PDFGenerator.generate(pages, to: "")
230 | } else {
231 | XCTFail("invalid page(s) type found.")
232 | }
233 | XCTFail("[\($0)] No create PDF from empty name image path.")
234 | } catch PDFGenerateError.emptyOutputPath {
235 | // Right Error
236 | } catch (let e) {
237 | XCTFail("[\($0)] Unknown or wrong error occurred.\(e)")
238 | }
239 | }
240 |
241 | // MARK: check EmptyPage
242 | emptyMocks.forEach {
243 | do {
244 | let path = PDFfilePath("test_sample1.pdf")
245 | if let pages = $0 as? [UIView] {
246 | try PDFGenerator.generate(pages, to: path)
247 | } else if let pages = $0 as? [UIImage] {
248 | try PDFGenerator.generate(pages, to: path)
249 | } else if let pages = $0 as? [String] {
250 | try PDFGenerator.generate(pages, to: path)
251 | } else if let pages = $0 as? [PDFPage] {
252 | try PDFGenerator.generate(pages, to: path)
253 | } else {
254 | XCTFail("invalid pages type found.")
255 | }
256 | XCTFail("[\($0)] No create PDF from empty name image path.")
257 | } catch PDFGenerateError.emptyPage {
258 | // Right Error
259 | } catch (let e) {
260 | XCTFail("[\($0)] Unknown or wrong error occurred.\(e)")
261 | }
262 | }
263 |
264 | // MARK: check EmptyPage
265 | emptyMocks.forEach {
266 | do {
267 | if let pages = $0 as? [UIView] {
268 | _ = try PDFGenerator.generated(by: pages)
269 | } else if let pages = $0 as? [UIImage] {
270 | _ = try PDFGenerator.generated(by: pages)
271 | } else if let pages = $0 as? [String] {
272 | _ = try PDFGenerator.generated(by: pages)
273 | } else if let pages = $0 as? [PDFPage] {
274 | _ = try PDFGenerator.generated(by: pages)
275 | } else {
276 | XCTFail("invalid pages type found.")
277 | }
278 | XCTFail("[\($0)] No create PDF from empty name image path.")
279 | } catch PDFGenerateError.emptyPage {
280 | // Right Error
281 | } catch (let e) {
282 | XCTFail("[\($0)] Unknown or wrong error occurred.\(e)")
283 | }
284 | }
285 |
286 | // MARK: check ZeroSizeView
287 | let emptyView = Mock.view(CGSize.zero)
288 | do {
289 | let path = PDFfilePath("test_sample2.pdf")
290 | try PDFGenerator.generate(emptyView, to: path)
291 | } catch PDFGenerateError.zeroSizeView(let v) {
292 | XCTAssertEqual(emptyView, v)
293 | } catch (let e) {
294 | XCTFail("Unknown or wrong error occurred.\(e)")
295 | }
296 | do {
297 | _ = try PDFGenerator.generated(by: emptyView)
298 | } catch PDFGenerateError.zeroSizeView(let v) {
299 | XCTAssertEqual(emptyView, v)
300 | } catch (let e) {
301 | XCTFail("Unknown or wrong error occurred.\(e)")
302 | }
303 | do {
304 | _ = try PDFGenerator.generated(by: [emptyView])
305 | } catch PDFGenerateError.zeroSizeView(let v) {
306 | XCTAssertEqual(emptyView, v)
307 | } catch (let e) {
308 | XCTFail("Unknown or wrong error occurred.\(e)")
309 | }
310 |
311 | let emptyViewPage = PDFPage.view(emptyView)
312 | do {
313 | let path = PDFfilePath("test_sample3.pdf")
314 | try PDFGenerator.generate(emptyViewPage, to: path)
315 | } catch PDFGenerateError.zeroSizeView(let v) {
316 | XCTAssertEqual(emptyView, v)
317 | } catch (let e) {
318 | XCTFail("Unknown or wrong error occurred.\(e)")
319 | }
320 | do {
321 | _ = try PDFGenerator.generated(by: emptyViewPage)
322 | } catch PDFGenerateError.zeroSizeView(let v) {
323 | XCTAssertEqual(emptyView, v)
324 | } catch (let e) {
325 | XCTFail("Unknown or wrong error occurred.\(e)")
326 | }
327 | do {
328 | _ = try PDFGenerator.generated(by: [emptyViewPage])
329 | } catch PDFGenerateError.zeroSizeView(let v) {
330 | XCTAssertEqual(emptyView, v)
331 | } catch (let e) {
332 | XCTFail("Unknown or wrong error occurred.\(e)")
333 | }
334 |
335 | // MARK: check ImageLoadFailed
336 | let wrongImagePath = "wrong/image.png"
337 | do {
338 | let path = PDFfilePath("test_sample4.pdf")
339 | try PDFGenerator.generate(wrongImagePath, to: path)
340 | } catch PDFGenerateError.imageLoadFailed(let ip) {
341 | XCTAssertEqual(wrongImagePath, ip as? String)
342 | } catch (let e) {
343 | XCTFail("Unknown or wrong error occurred.\(e)")
344 | }
345 | do {
346 | _ = try PDFGenerator.generated(by: wrongImagePath)
347 | } catch PDFGenerateError.imageLoadFailed(let ip) {
348 | XCTAssertEqual(wrongImagePath, ip as? String)
349 | } catch (let e) {
350 | XCTFail("Unknown or wrong error occurred.\(e)")
351 | }
352 | do {
353 | _ = try PDFGenerator.generated(by: [wrongImagePath])
354 | } catch PDFGenerateError.imageLoadFailed(let ip) {
355 | XCTAssertEqual(wrongImagePath, ip as? String)
356 | } catch (let e) {
357 | XCTFail("Unknown or wrong error occurred.\(e)")
358 | }
359 |
360 | let wrongImagePathPage = PDFPage.imagePath(wrongImagePath)
361 | do {
362 | let path = PDFfilePath("test_sample5.pdf")
363 | try PDFGenerator.generate(wrongImagePathPage, to: path)
364 | } catch PDFGenerateError.imageLoadFailed(let ip) {
365 | XCTAssertEqual(wrongImagePath, ip as? String)
366 | } catch (let e) {
367 | XCTFail("Unknown or wrong error occurred.\(e)")
368 | }
369 | do {
370 | _ = try PDFGenerator.generated(by: wrongImagePathPage)
371 | } catch PDFGenerateError.imageLoadFailed(let ip) {
372 | XCTAssertEqual(wrongImagePath, ip as? String)
373 | } catch (let e) {
374 | XCTFail("Unknown or wrong error occurred.\(e)")
375 | }
376 | do {
377 | _ = try PDFGenerator.generated(by: [wrongImagePathPage])
378 | } catch PDFGenerateError.imageLoadFailed(let ip) {
379 | XCTAssertEqual(wrongImagePath, ip as? String)
380 | } catch (let e) {
381 | XCTFail("Unknown or wrong error occurred.\(e)")
382 | }
383 |
384 | let wrongData = Data()
385 |
386 | do {
387 | _ = try PDFGenerator.generated(by: PDFPage.binary(wrongData))
388 | } catch PDFGenerateError.imageLoadFailed(let data) {
389 | XCTAssertEqual(wrongData, data as? Data)
390 | } catch (let e) {
391 | XCTFail("Unknown or wrong error occurred.\(e)")
392 | }
393 |
394 | }
395 | // swiftlint:enable function_body_length
396 |
397 | func testPDFPassword() {
398 | let view = Mock.view(CGSize(width: 100, height: 100))
399 | let view2 = Mock.view(CGSize(width: 100, height: 100))
400 |
401 | let path1 = PDFfilePath("test_sample1.pdf")
402 | _ = try? PDFGenerator.generate(view, to: path1, password: "abcdef")
403 | XCTAssertTrue(isExistPDF(path1))
404 |
405 | let path2 = PDFfilePath("test_sample2.pdf")
406 | _ = try? PDFGenerator.generate(view, to: path2, password: "⌘123456")
407 | XCTAssertFalse(isExistPDF(path2))
408 |
409 | let path3 = PDFfilePath("test_sample3.pdf")
410 | do {
411 | try PDFGenerator.generate([view, view2], to: path3, password: "123456")
412 | } catch {
413 | XCTFail()
414 | }
415 |
416 | let path4 = PDFfilePath("test_sample4.pdf")
417 | do {
418 | try PDFGenerator.generate([view, view2], to: path4, password: "⌘123456")
419 | XCTFail()
420 | } catch PDFGenerateError.invalidPassword(let password) {
421 | XCTAssertEqual(password, "⌘123456")
422 | } catch {
423 | XCTFail()
424 | }
425 |
426 | let path5 = PDFfilePath("test_sample5.pdf")
427 | do {
428 | try PDFGenerator.generate([view, view2], to: path5, password: "0123456789abcdef0123456789abcdefA")
429 | XCTFail()
430 | } catch PDFGenerateError.tooLongPassword(let length) {
431 | XCTAssertEqual(length, 33)
432 | } catch {
433 | XCTFail()
434 | }
435 |
436 | XCTAssertNotNil(try? PDFGenerator.generated(by: view, password: "abcdef"))
437 | XCTAssertNil(try? PDFGenerator.generated(by: [view], password: "⌘123456"))
438 |
439 | do {
440 | _ = try PDFGenerator.generated(by: [view], password: "123456")
441 | } catch {
442 | XCTFail()
443 | }
444 |
445 | do {
446 | _ = try PDFGenerator.generated(by: [view], password: "⌘123456")
447 | } catch PDFGenerateError.invalidPassword(let password) {
448 | XCTAssertEqual(password, "⌘123456")
449 | } catch {
450 | XCTFail()
451 | }
452 |
453 | do {
454 | _ = try PDFGenerator.generated(by: [view], password: "0123456789abcdef0123456789abcdefA")
455 | XCTFail()
456 | } catch PDFGenerateError.tooLongPassword(let length) {
457 | XCTAssertEqual(length, 33)
458 | } catch {
459 | XCTFail()
460 | }
461 | }
462 | }
463 |
--------------------------------------------------------------------------------
/PDFGeneratorTests/PDFPasswordTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDFPasswordTests.swift
3 | // PDFGenerator
4 | //
5 | // Created by Suguru Kishimoto on 7/23/16.
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import PDFGenerator
11 | class PDFPasswordTests: XCTestCase {
12 | func test() {
13 | let p1: PDFPassword = "123456"
14 | XCTAssertEqual(p1.userPassword, "123456")
15 | XCTAssertEqual(p1.ownerPassword, "123456")
16 | do {
17 | try p1.verify()
18 | } catch _ {
19 | XCTFail()
20 | }
21 |
22 | let p2: PDFPassword = PDFPassword(user: "123456", owner: "abcdef")
23 | XCTAssertNotEqual(p2.userPassword, p2.ownerPassword)
24 | do {
25 | try p2.verify()
26 | } catch _ {
27 | XCTFail()
28 | }
29 |
30 | let p3: PDFPassword = PDFPassword(user: "ああああ", owner: "abcdef")
31 | do {
32 | try p3.verify()
33 | XCTFail()
34 | } catch PDFGenerateError.invalidPassword(let password) {
35 | XCTAssertEqual(p3.userPassword, password)
36 | } catch _ {
37 | XCTFail()
38 | }
39 |
40 | let p4: PDFPassword = PDFPassword(user: "123456", owner: "ああああ")
41 | do {
42 | try p4.verify()
43 | XCTFail()
44 | } catch PDFGenerateError.invalidPassword(let password) {
45 | XCTAssertEqual(p4.ownerPassword, password)
46 | } catch _ {
47 | XCTFail()
48 | }
49 |
50 | let p5: PDFPassword = PDFPassword(user: "1234567890123456789012345678901234567890", owner: "abcdef")
51 | do {
52 | try p5.verify()
53 | XCTFail()
54 | } catch PDFGenerateError.tooLongPassword(let length) {
55 | XCTAssertEqual(p5.userPassword.count, length)
56 | } catch _ {
57 | XCTFail()
58 | }
59 |
60 | let p6: PDFPassword = PDFPassword(user: "123456", owner: "abcdefghijabcdefghijabcdefghijabcdefghij")
61 | do {
62 | try p6.verify()
63 | XCTFail()
64 | } catch PDFGenerateError.tooLongPassword(let length) {
65 | XCTAssertEqual(p6.ownerPassword.count, length)
66 | } catch _ {
67 | XCTFail()
68 | }
69 |
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Features |
3 | Requirements |
4 | Installation |
5 | Usage |
6 | Communication |
7 | LICENSE
8 |
9 |
10 | # PDFGenerator
11 | [](https://travis-ci.org/sgr-ksmt/PDFGenerator)
12 | [](https://github.com/sgr-ksmt/PDFGenerator/releases)
13 | [](https://codecov.io/gh/sgr-ksmt/PDFGenerator)
14 | []()
15 | [](https://github.com/Carthage/Carthage)
16 | [](https://cocoapods.org/pods/PDFGenerator)
17 | []()
18 | [](https://github.com/matteocrippa/awesome-swift#pdf)
19 | [](https://houndci.com)
20 |
21 |
22 | `PDFGenerator` is a simple PDF generator that generates with `UIView`, `UIImage`, ...etc .
23 |
24 | ```swift
25 | do {
26 | let page: [PDFPage] = [
27 | .whitePage(CGSize(width: 200.0, height: 100.0)),
28 | .image(image1)
29 | .image(image2)
30 | .imagePath(lastPageImagePath)
31 | .whitePage(CGSize(width: 200.0, height: 100.0))
32 | ]
33 | let path = NSTemporaryDirectory().appending("sample1.pdf")
34 | try PDFGenerator.generate(page, to: path, password: "123456")
35 | } catch let error {
36 | print(error)
37 | }
38 | ```
39 |
40 | ## Features
41 | - **Swift 5 is ready** :pray:
42 | - Multiple pages support.
43 | - Also generate PDF with `image path`, `image binary`, `image ref (CGImage)`
44 | - Good memory management.
45 | - UIScrollView support : If view is `UIScrollView`, `UITableView`, `UICollectionView`, `UIWebView`, drawn whole content.
46 | - Outputs as binary(`Data`) or writes to Disk(in given file path) directly.
47 | - Corresponding to Error-Handling. Strange PDF has never been generated!!.
48 | - DPI support. : Default dpi is 72.
49 | - Password protection support.
50 |
51 | ## Requirements
52 | - iOS 9.0+
53 | - Xcode 11+
54 | - Swift 5.1
55 |
56 | ## Installation
57 |
58 | ### Carthage
59 |
60 | - Add the following to your *Cartfile*:
61 |
62 | ```bash
63 | github "sgr-ksmt/PDFGenerator" ~> 3.1
64 | ```
65 |
66 | - Then run command:
67 |
68 | ```bash
69 | $ carthage update
70 | ```
71 |
72 | - Add the framework as described.
73 |
Details: [Carthage Readme](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)
74 |
75 |
76 | #### CocoaPods
77 |
78 | **PDFGenerator** is available through [CocoaPods](http://cocoapods.org). To install
79 | it, simply add the following line to your Podfile:
80 |
81 | ```ruby
82 | pod 'PDFGenerator', '~> 3.1'
83 | ```
84 |
85 | and run `pod install`
86 |
87 | ## Usage
88 |
89 | ### Generate from view(s) or image(s)
90 | - UIView → PDF
91 |
92 | ```swift
93 | func generatePDF() {
94 | let v1 = UIScrollView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 100.0))
95 | let v2 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 200.0))
96 | let v3 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 200.0))
97 | v1.backgroundColor = .red
98 | v1.contentSize = CGSize(width: 100.0, height: 200.0)
99 | v2.backgroundColor = .green
100 | v3.backgroundColor = .blue
101 |
102 | let dst = URL(fileURLWithPath: NSTemporaryDirectory().appending("sample1.pdf"))
103 | // outputs as Data
104 | do {
105 | let data = try PDFGenerator.generated(by: [v1, v2, v3])
106 | try data.write(to: dst, options: .atomic)
107 | } catch (let error) {
108 | print(error)
109 | }
110 |
111 | // writes to Disk directly.
112 | do {
113 | try PDFGenerator.generate([v1, v2, v3], to: dst)
114 | } catch (let error) {
115 | print(error)
116 | }
117 | }
118 | ```
119 |
120 | `Also PDF can generate from image(s), image path(s) same as example.`
121 |
122 | ### Generate from PDFPage object
123 |
124 | - (UIVIew or UIImage) → PDF
125 |
126 | Use `PDFPage`.
127 |
128 | ```swift
129 | public enum PDFPage {
130 | case whitePage(CGSize) // = A white view
131 | case view(UIView)
132 | case image(UIImage)
133 | case imagePath(String)
134 | case binary(Data)
135 | case imageRef(CGImage)
136 | }
137 | ```
138 |
139 | ```swift
140 | func generatePDF() {
141 | let v1 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 100.0))
142 | v1.backgroundColor = .red
143 | let v2 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 200.0))
144 | v2.backgroundColor = .green
145 |
146 | let page1 = PDFPage.View(v1)
147 | let page2 = PDFPage.View(v2)
148 | let page3 = PDFPage.WhitePage(CGSizeMake(200, 100))
149 | let page4 = PDFPage.Image(UIImage(contentsOfFile: "path/to/image1.png")!)
150 | let page5 = PDFPage.ImagePath("path/to/image2.png")
151 | let pages = [page1, page2, page3, page4, page5]
152 |
153 | let dst = NSTemporaryDirectory().appending("sample1.pdf")
154 | do {
155 | try PDFGenerator.generate(pages, to: dst)
156 | } catch (let e) {
157 | print(e)
158 | }
159 | }
160 | ```
161 |
162 | ### Generate custom dpi PDF
163 | ```swift
164 | // generate dpi300 PDF (default: 72dpi)
165 | func generatePDF() {
166 | let v1 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 100.0))
167 | v1.backgroundColor = .red
168 | let v2 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 200.0))
169 | v2.backgroundColor = .green
170 |
171 | let page1 = PDFPage.View(v1)
172 | let page2 = PDFPage.View(v2)
173 | let pages = [page1, page2]
174 |
175 | let dst = NSTemporaryDirectory().appending("sample1.pdf")
176 | do {
177 | try PDFGenerator.generate(pages, to: dst, dpi: .dpi_300)
178 | } catch (let e) {
179 | print(e)
180 | }
181 | }
182 | ```
183 |
184 | ### Password protection
185 | ```swift
186 | // generate PDF with password: 123456
187 | func generatePDF() {
188 | let v1 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 100.0))
189 | v1.backgroundColor = .red
190 | let v2 = UIView(frame: CGRect(x: 0.0,y: 0, width: 100.0, height: 200.0))
191 | v2.backgroundColor = .green
192 |
193 | let page1 = PDFPage.view(v1)
194 | let page2 = PDFPage.view(v2)
195 | let pages = [page1, page2]
196 |
197 | let dst = NSTemporaryDirectory().appending("sample1.pdf")
198 | do {
199 | try PDFGenerator.generate(pages, to: dst, password: "123456")
200 | // or use PDFPassword model
201 | try PDFGenerator.generate(pages, to: dst, password: PDFPassword("123456"))
202 | // or use PDFPassword model and set user/owner password
203 | try PDFGenerator.generate(pages, to: dst, password: PDFPassword(user: "123456", owner: "abcdef"))
204 | } catch let error {
205 | print(error)
206 | }
207 | }
208 | ```
209 |
210 | ## Communication
211 | - If you found a bug, please open an issue. :bow:
212 | - Also, if you have a feature request, please open an issue. :thumbsup:
213 | - If you want to contribute, submit a pull request.:muscle:
214 |
215 | ## License
216 |
217 | **PDFGenerator** is under MIT license. See the [LICENSE](LICENSE) file for more info.
218 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | ignore:
3 | - "Demo/*"
4 | - "PDFGeneratorTests/*"
5 |
6 | comment: false
7 |
--------------------------------------------------------------------------------