From 063be62da2900a67c2d7b21e53e0428ab9302085 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Wed, 8 May 2019 12:47:01 +0200 Subject: [PATCH 1/8] Updated to support Vapor 3 --- Package.resolved | 34 +++++++++ Package.swift | 21 +++++- Sources/wkhtmltopdf/Document+Generate.swift | 75 ++++++++++--------- Sources/wkhtmltopdf/Document.swift | 32 ++++---- Sources/wkhtmltopdf/Page+View.swift | 13 ---- Sources/wkhtmltopdf/Page.swift | 18 ++--- Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift | 12 +++ Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift | 6 +- 8 files changed, 131 insertions(+), 80 deletions(-) create mode 100644 Package.resolved delete mode 100644 Sources/wkhtmltopdf/Page+View.swift create mode 100644 Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..14bd5f5 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "Core", + "repositoryURL": "https://github.com/vapor/core.git", + "state": { + "branch": null, + "revision": "2731f8ba0cf274a61c9bd6ab43550f692ffaf879", + "version": "3.9.0" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", + "version": "1.14.1" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index befbd42..22374dd 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,27 @@ +// swift-tools-version:4.2 import PackageDescription let package = Package( name: "wkhtmltopdf", + products: [ + // Products define the executables and libraries produced by a package, and make them visible to other packages. + .library( + name: "wkhtmltopdf", + targets: ["wkhtmltopdf"]), + ], dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), + .package(url: "https://github.com/vapor/core.git", from: "3.0.0") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages which this package depends on. + .target( + name: "wkhtmltopdf", + dependencies: [ + "Core" + ]), + .testTarget( + name: "wkhtmltopdfTests", + dependencies: ["wkhtmltopdf"]), ] ) diff --git a/Sources/wkhtmltopdf/Document+Generate.swift b/Sources/wkhtmltopdf/Document+Generate.swift index cd752a7..3c070bd 100644 --- a/Sources/wkhtmltopdf/Document+Generate.swift +++ b/Sources/wkhtmltopdf/Document+Generate.swift @@ -1,5 +1,6 @@ import Foundation import Core +import Core #if os(Linux) && !swift(>=3.1) typealias Process = Task @@ -7,42 +8,42 @@ typealias Process = Task extension Document { - public func generatePDF() throws -> Bytes { - // Create the temp folder if it doesn't already exist - let workDir = "/tmp/vapor-wkhtmltopdf" - try FileManager().createDirectory(atPath: workDir, withIntermediateDirectories: true) - // Save input pages to temp files, and build up args to wkhtmltopdf - var wkArgs: [String] = [ - "--zoom", Document.zoom, - "--quiet", - "-s", paperSize, - "-T", "\(topMargin)mm", - "-R", "\(rightMargin)mm", - "-B", "\(bottomMargin)mm", - "-L", "\(leftMargin)mm", - ] - let fm = DataFile(workDir: workDir) - let pageFiles: [String] = try pages.map { page in - let fileName = "\(workDir)/\(UUID().uuidString).html" - try fm.write(page.content, to: fileName) - return fileName - } - defer { - pageFiles.forEach { path in - try? fm.delete(at: path) - } - } - wkArgs += pageFiles - // Call wkhtmltopdf and retrieve the result data - let wk = Process() - let stdout = Pipe() - wk.launchPath = "/usr/local/bin/wkhtmltopdf" - wk.arguments = wkArgs - wk.arguments?.append("-") // output to stdout - wk.standardOutput = stdout - wk.launch() - let pdf = stdout.fileHandleForReading.readDataToEndOfFile() - return pdf.makeBytes() - } + public func generatePDF() throws -> Data { + let fileManager = FileManager.default + // Create the temp folder if it doesn't already exist + let workDir = "/tmp/vapor-wkhtmltopdf" + try fileManager.createDirectory(atPath: workDir, withIntermediateDirectories: true) + // Save input pages to temp files, and build up args to wkhtmltopdf + var wkArgs: [String] = [ + "--zoom", Document.zoom, + "--quiet", + "-s", paperSize, + "-T", "\(topMargin)mm", + "-R", "\(rightMargin)mm", + "-B", "\(bottomMargin)mm", + "-L", "\(leftMargin)mm", + ] + let pageFiles: [String] = try pages.map { page in + let name = UUID().uuidString + ".html" + let filename = "\(workDir)/\(name)" + try File(data: page.content, filename: filename).data.write(to: URL(fileURLWithPath: filename)) + return filename + } + defer { + try? pageFiles.forEach(fileManager.removeItem) + } + + wkArgs += pageFiles + // Call wkhtmltopdf and retrieve the result data + let wk = Process() + let stdout = Pipe() + wk.launchPath = "/usr/local/bin/wkhtmltopdf" + wk.arguments = wkArgs + wk.arguments?.append("-") // output to stdout + wk.standardOutput = stdout + wk.launch() + let pdf = stdout.fileHandleForReading.readDataToEndOfFile() + return pdf + } } diff --git a/Sources/wkhtmltopdf/Document.swift b/Sources/wkhtmltopdf/Document.swift index 165c15e..bbb90c1 100644 --- a/Sources/wkhtmltopdf/Document.swift +++ b/Sources/wkhtmltopdf/Document.swift @@ -1,23 +1,23 @@ public class Document { - // This may need changing across different platforms and deployments. - static var zoom: String = "1.3" + // This may need changing across different platforms and deployments. + static var zoom: String = "1.3" - let topMargin: Int - let rightMargin: Int - let bottomMargin: Int - let leftMargin: Int + let topMargin: Int + let rightMargin: Int + let bottomMargin: Int + let leftMargin: Int - let paperSize: String + let paperSize: String - public var pages: [Page] = [] - - public init(size: String = "A4", margins all: Int? = nil, top: Int? = nil, right: Int? = nil, bottom: Int? = nil, left: Int? = nil) { - paperSize = size - topMargin = all ?? top ?? 20 - rightMargin = all ?? right ?? 20 - bottomMargin = all ?? bottom ?? 20 - leftMargin = all ?? left ?? 20 - } + public var pages: [Page] = [] + + public init(size: String = "A4", margins all: Int? = nil, top: Int? = nil, right: Int? = nil, bottom: Int? = nil, left: Int? = nil) { + paperSize = size + topMargin = all ?? top ?? 20 + rightMargin = all ?? right ?? 20 + bottomMargin = all ?? bottom ?? 20 + leftMargin = all ?? left ?? 20 + } } diff --git a/Sources/wkhtmltopdf/Page+View.swift b/Sources/wkhtmltopdf/Page+View.swift deleted file mode 100644 index 26bc3ef..0000000 --- a/Sources/wkhtmltopdf/Page+View.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Vapor - -extension Page { - - public init(_ drop: Droplet, view path: String, _ context: Node? = nil) throws { - var ctx = context ?? Node.object([:]) - ctx["workDir"] = drop.config.workDir.makeNode(in: nil) - ctx["publicDir"] = (drop.config.workDir + "Public/").makeNode(in: nil) - let view = try drop.view.make(path, ctx) - self.init(view.data) - } - -} diff --git a/Sources/wkhtmltopdf/Page.swift b/Sources/wkhtmltopdf/Page.swift index c90c830..d05d38c 100644 --- a/Sources/wkhtmltopdf/Page.swift +++ b/Sources/wkhtmltopdf/Page.swift @@ -1,15 +1,13 @@ -import Bits +import Foundation public struct Page { + let content: Data - let content: Bytes - - public init(_ content: Bytes) { - self.content = content - } - - public init(_ content: String) { - self.content = content.makeBytes() - } + public init(_ content: Data) { + self.content = content + } + public init(_ content: String) { + self.content = Data(content.utf8) + } } diff --git a/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift b/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift new file mode 100644 index 0000000..e26d276 --- /dev/null +++ b/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift @@ -0,0 +1,12 @@ +// +// WkHtmlToPdfProvider.swift +// Async +// +// Created by Kim de Vos on 08/05/2019. +// + +//import Vapor +// +//public final class StripeProvider: Provider { +// +//} diff --git a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift index 8df3bae..6bdbd56 100644 --- a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift +++ b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift @@ -1,6 +1,5 @@ import XCTest @testable import wkhtmltopdf -import Core class wkhtmltopdfTests: XCTestCase { static var allTests = [ @@ -15,8 +14,9 @@ class wkhtmltopdfTests: XCTestCase { // Cop-out test, just ensuring that the returned data is something XCTAssert(data.count > 50) // Visual test - let fm = DataFile(workDir: "/tmp/vapor-wkhtmltopdf") - try fm.write(data, to: "/tmp/vapor-wkhtmltopdf/testOutput.pdf") + + FileManager.default.createFile(atPath: "/tmp/vapor-wkhtmltopdf/testOutput.pdf", contents: data, attributes: nil) + print("Test output PDF can be viewed at /tmp/vapor-wkhtmltopdf/testOutput.pdf") } } From 8bdaa74ba35dc53a6fc1c090937514a07d83d66f Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Thu, 9 May 2019 08:06:22 +0200 Subject: [PATCH 2/8] Removed provider as it was a fail --- Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift diff --git a/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift b/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift deleted file mode 100644 index e26d276..0000000 --- a/Sources/wkhtmltopdf/WkHtmlToPdfProvider.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// WkHtmlToPdfProvider.swift -// Async -// -// Created by Kim de Vos on 08/05/2019. -// - -//import Vapor -// -//public final class StripeProvider: Provider { -// -//} From 32fe671d6d079d1db5310154e3b15beac1cb81ff Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Thu, 9 May 2019 08:06:34 +0200 Subject: [PATCH 3/8] Added Vapor as dependency --- Package.resolved | 126 +++++++++++++++++++++++++++++++++++++++++++++++ Package.swift | 4 +- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index 14bd5f5..0a41dc7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "Console", + "repositoryURL": "https://github.com/vapor/console.git", + "state": { + "branch": null, + "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", + "version": "3.1.1" + } + }, { "package": "Core", "repositoryURL": "https://github.com/vapor/core.git", @@ -10,6 +19,60 @@ "version": "3.9.0" } }, + { + "package": "Crypto", + "repositoryURL": "https://github.com/vapor/crypto.git", + "state": { + "branch": null, + "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", + "version": "3.3.3" + } + }, + { + "package": "DatabaseKit", + "repositoryURL": "https://github.com/vapor/database-kit.git", + "state": { + "branch": null, + "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", + "version": "1.3.3" + } + }, + { + "package": "HTTP", + "repositoryURL": "https://github.com/vapor/http.git", + "state": { + "branch": null, + "revision": "254a0a0cbf22a02b697a075a0d2ddbb448bb7c87", + "version": "3.2.0" + } + }, + { + "package": "Multipart", + "repositoryURL": "https://github.com/vapor/multipart.git", + "state": { + "branch": null, + "revision": "f919a01c4d10a281d6236a21b0b1d1759a72b8eb", + "version": "3.0.4" + } + }, + { + "package": "Routing", + "repositoryURL": "https://github.com/vapor/routing.git", + "state": { + "branch": null, + "revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c", + "version": "3.0.2" + } + }, + { + "package": "Service", + "repositoryURL": "https://github.com/vapor/service.git", + "state": { + "branch": null, + "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", + "version": "1.0.2" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", @@ -19,6 +82,24 @@ "version": "1.14.1" } }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", + "version": "1.4.0" + } + }, + { + "package": "swift-nio-ssl-support", + "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", + "state": { + "branch": null, + "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", + "version": "1.0.0" + } + }, { "package": "swift-nio-zlib-support", "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", @@ -27,6 +108,51 @@ "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", "version": "1.0.0" } + }, + { + "package": "TemplateKit", + "repositoryURL": "https://github.com/vapor/template-kit.git", + "state": { + "branch": null, + "revision": "121ae51433df94cf6e15c09e1f1b0f7c77ff8d5c", + "version": "1.2.0" + } + }, + { + "package": "URLEncodedForm", + "repositoryURL": "https://github.com/vapor/url-encoded-form.git", + "state": { + "branch": null, + "revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed", + "version": "1.0.6" + } + }, + { + "package": "Validation", + "repositoryURL": "https://github.com/vapor/validation.git", + "state": { + "branch": null, + "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", + "version": "2.1.1" + } + }, + { + "package": "Vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6", + "version": "3.3.0" + } + }, + { + "package": "WebSocket", + "repositoryURL": "https://github.com/vapor/websocket.git", + "state": { + "branch": null, + "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", + "version": "1.1.2" + } } ] }, diff --git a/Package.swift b/Package.swift index 22374dd..854b98e 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( targets: ["wkhtmltopdf"]), ], dependencies: [ - .package(url: "https://github.com/vapor/core.git", from: "3.0.0") + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -18,7 +18,7 @@ let package = Package( .target( name: "wkhtmltopdf", dependencies: [ - "Core" + "Vapor" ]), .testTarget( name: "wkhtmltopdfTests", From 66a9af6585e6b8c43b87a711c2d2c9dae61f6842 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Thu, 9 May 2019 08:07:06 +0200 Subject: [PATCH 4/8] Using `BlockingIOThreadPool` when creating pdf file --- Sources/wkhtmltopdf/Document+Generate.swift | 77 ++++++++++--------- Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift | 45 +++++++---- 2 files changed, 68 insertions(+), 54 deletions(-) diff --git a/Sources/wkhtmltopdf/Document+Generate.swift b/Sources/wkhtmltopdf/Document+Generate.swift index 3c070bd..a6b4bfb 100644 --- a/Sources/wkhtmltopdf/Document+Generate.swift +++ b/Sources/wkhtmltopdf/Document+Generate.swift @@ -1,6 +1,5 @@ import Foundation -import Core -import Core +import Vapor #if os(Linux) && !swift(>=3.1) typealias Process = Task @@ -8,42 +7,46 @@ typealias Process = Task extension Document { - public func generatePDF() throws -> Data { - let fileManager = FileManager.default - // Create the temp folder if it doesn't already exist - let workDir = "/tmp/vapor-wkhtmltopdf" - try fileManager.createDirectory(atPath: workDir, withIntermediateDirectories: true) - // Save input pages to temp files, and build up args to wkhtmltopdf - var wkArgs: [String] = [ - "--zoom", Document.zoom, - "--quiet", - "-s", paperSize, - "-T", "\(topMargin)mm", - "-R", "\(rightMargin)mm", - "-B", "\(bottomMargin)mm", - "-L", "\(leftMargin)mm", - ] + public func generatePDF(on req: Request) throws -> Future { + let sharedThreadPool = try req.make(BlockingIOThreadPool.self) - let pageFiles: [String] = try pages.map { page in - let name = UUID().uuidString + ".html" - let filename = "\(workDir)/\(name)" - try File(data: page.content, filename: filename).data.write(to: URL(fileURLWithPath: filename)) - return filename - } - defer { - try? pageFiles.forEach(fileManager.removeItem) - } + return sharedThreadPool.runIfActive(eventLoop: req.eventLoop) { () -> Data in + let fileManager = FileManager.default + // Create the temp folder if it doesn't already exist + let workDir = "/tmp/vapor-wkhtmltopdf" + try fileManager.createDirectory(atPath: workDir, withIntermediateDirectories: true) + // Save input pages to temp files, and build up args to wkhtmltopdf + var wkArgs: [String] = [ + "--zoom", Document.zoom, + "--quiet", + "-s", self.paperSize, + "-T", "\(self.topMargin)mm", + "-R", "\(self.rightMargin)mm", + "-B", "\(self.bottomMargin)mm", + "-L", "\(self.leftMargin)mm", + ] - wkArgs += pageFiles - // Call wkhtmltopdf and retrieve the result data - let wk = Process() - let stdout = Pipe() - wk.launchPath = "/usr/local/bin/wkhtmltopdf" - wk.arguments = wkArgs - wk.arguments?.append("-") // output to stdout - wk.standardOutput = stdout - wk.launch() - let pdf = stdout.fileHandleForReading.readDataToEndOfFile() - return pdf + let pageFiles: [String] = try self.pages.map { page in + let name = UUID().uuidString + ".html" + let filename = "\(workDir)/\(name)" + try File(data: page.content, filename: filename).data.write(to: URL(fileURLWithPath: filename)) + return filename + } + defer { + try? pageFiles.forEach(fileManager.removeItem) + } + + wkArgs += pageFiles + // Call wkhtmltopdf and retrieve the result data + let wk = Process() + let stdout = Pipe() + wk.launchPath = "/usr/local/bin/wkhtmltopdf" + wk.arguments = wkArgs + wk.arguments?.append("-") // output to stdout + wk.standardOutput = stdout + wk.launch() + let pdf = stdout.fileHandleForReading.readDataToEndOfFile() + return pdf + } } } diff --git a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift index 6bdbd56..f99cf42 100644 --- a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift +++ b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift @@ -1,22 +1,33 @@ import XCTest +import Vapor @testable import wkhtmltopdf class wkhtmltopdfTests: XCTestCase { - static var allTests = [ - ("testStringPDF", testStringPDF), - ] - - func testStringPDF() throws { - let document = Document(margins: 15) - let page1 = Page("

Page from direct HTML

") - document.pages = [page1] - let data = try document.generatePDF() - // Cop-out test, just ensuring that the returned data is something - XCTAssert(data.count > 50) - // Visual test - - FileManager.default.createFile(atPath: "/tmp/vapor-wkhtmltopdf/testOutput.pdf", contents: data, attributes: nil) - - print("Test output PDF can be viewed at /tmp/vapor-wkhtmltopdf/testOutput.pdf") - } + + var app: Application! + + override func setUp() { + super.setUp() + + app = try! Application(config: .default(), environment: .testing, services: .default()) + } + + func testStringPDF() throws { + let req = Request(using: app) + let document = Document(margins: 15) + let page1 = Page("

Page from direct HTML

") + document.pages = [page1] + let data = try document.generatePDF(on: req).wait() + // Cop-out test, just ensuring that the returned data is something + XCTAssert(data.count > 50) + // Visual test + + FileManager.default.createFile(atPath: "/tmp/vapor-wkhtmltopdf/testOutput.pdf", contents: data, attributes: nil) + + print("Test output PDF can be viewed at /tmp/vapor-wkhtmltopdf/testOutput.pdf") + } + + static var allTests = [ + ("testStringPDF", testStringPDF), + ] } From ea7e79d2bfafa9bb391959719adeea7e7748c8f3 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Thu, 9 May 2019 11:41:03 +0200 Subject: [PATCH 5/8] Use Container instead of Request. --- Package.swift | 7 ++++--- Sources/wkhtmltopdf/Document+Generate.swift | 8 ++++---- Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift | 3 +-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 854b98e..15447ab 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,8 @@ let package = Package( targets: ["wkhtmltopdf"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0") + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + .package(url: "https://github.com/vapor/service.git", from: "1.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -18,10 +19,10 @@ let package = Package( .target( name: "wkhtmltopdf", dependencies: [ - "Vapor" + "Service" ]), .testTarget( name: "wkhtmltopdfTests", - dependencies: ["wkhtmltopdf"]), + dependencies: ["wkhtmltopdf", "Vapor"]), ] ) diff --git a/Sources/wkhtmltopdf/Document+Generate.swift b/Sources/wkhtmltopdf/Document+Generate.swift index a6b4bfb..c266e2e 100644 --- a/Sources/wkhtmltopdf/Document+Generate.swift +++ b/Sources/wkhtmltopdf/Document+Generate.swift @@ -1,5 +1,5 @@ import Foundation -import Vapor +import Service #if os(Linux) && !swift(>=3.1) typealias Process = Task @@ -7,10 +7,10 @@ typealias Process = Task extension Document { - public func generatePDF(on req: Request) throws -> Future { - let sharedThreadPool = try req.make(BlockingIOThreadPool.self) + public func generatePDF(on container: Container) throws -> Future { + let sharedThreadPool = try container.make(BlockingIOThreadPool.self) - return sharedThreadPool.runIfActive(eventLoop: req.eventLoop) { () -> Data in + return sharedThreadPool.runIfActive(eventLoop: container.eventLoop) { () -> Data in let fileManager = FileManager.default // Create the temp folder if it doesn't already exist let workDir = "/tmp/vapor-wkhtmltopdf" diff --git a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift index f99cf42..42e235b 100644 --- a/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift +++ b/Tests/wkhtmltopdfTests/wkhtmltopdfTests.swift @@ -13,11 +13,10 @@ class wkhtmltopdfTests: XCTestCase { } func testStringPDF() throws { - let req = Request(using: app) let document = Document(margins: 15) let page1 = Page("

Page from direct HTML

") document.pages = [page1] - let data = try document.generatePDF(on: req).wait() + let data = try document.generatePDF(on: app).wait() // Cop-out test, just ensuring that the returned data is something XCTAssert(data.count > 50) // Visual test From fb88092c87bfa2d18c18bfe10fc86fcd72874f8a Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 10 May 2019 07:26:00 +0200 Subject: [PATCH 6/8] Applied feedback from @bygri --- Package.pins | 102 ------------- Package.resolved | 160 -------------------- Package.swift | 3 - Sources/wkhtmltopdf/Document+Generate.swift | 8 +- Sources/wkhtmltopdf/Document.swift | 7 +- 5 files changed, 5 insertions(+), 275 deletions(-) delete mode 100644 Package.pins delete mode 100644 Package.resolved diff --git a/Package.pins b/Package.pins deleted file mode 100644 index cc9d30e..0000000 --- a/Package.pins +++ /dev/null @@ -1,102 +0,0 @@ -{ - "autoPin": true, - "pins": [ - { - "package": "BCrypt", - "reason": null, - "repositoryURL": "https://github.com/vapor/bcrypt.git", - "version": "1.0.0" - }, - { - "package": "Bits", - "reason": null, - "repositoryURL": "https://github.com/vapor/bits.git", - "version": "1.0.0" - }, - { - "package": "Console", - "reason": null, - "repositoryURL": "https://github.com/vapor/console.git", - "version": "2.0.0" - }, - { - "package": "Core", - "reason": null, - "repositoryURL": "https://github.com/vapor/core.git", - "version": "2.0.0" - }, - { - "package": "Crypto", - "reason": null, - "repositoryURL": "https://github.com/vapor/crypto.git", - "version": "2.0.0" - }, - { - "package": "CTLS", - "reason": null, - "repositoryURL": "https://github.com/vapor/ctls.git", - "version": "1.0.0" - }, - { - "package": "Debugging", - "reason": null, - "repositoryURL": "https://github.com/vapor/debugging.git", - "version": "1.0.0" - }, - { - "package": "Engine", - "reason": null, - "repositoryURL": "https://github.com/vapor/engine.git", - "version": "2.0.0" - }, - { - "package": "JSON", - "reason": null, - "repositoryURL": "https://github.com/vapor/json.git", - "version": "2.0.0" - }, - { - "package": "Multipart", - "reason": null, - "repositoryURL": "https://github.com/vapor/multipart.git", - "version": "2.0.0" - }, - { - "package": "Node", - "reason": null, - "repositoryURL": "https://github.com/vapor/node.git", - "version": "2.0.0" - }, - { - "package": "Random", - "reason": null, - "repositoryURL": "https://github.com/vapor/random.git", - "version": "1.0.0" - }, - { - "package": "Routing", - "reason": null, - "repositoryURL": "https://github.com/vapor/routing.git", - "version": "2.0.0" - }, - { - "package": "Sockets", - "reason": null, - "repositoryURL": "https://github.com/vapor/sockets.git", - "version": "2.0.0" - }, - { - "package": "TLS", - "reason": null, - "repositoryURL": "https://github.com/vapor/tls.git", - "version": "2.0.0" - }, - { - "package": "Vapor", - "reason": null, - "repositoryURL": "https://github.com/vapor/vapor.git", - "version": "2.0.0" - } - ], - "version": 1 -} \ No newline at end of file diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 0a41dc7..0000000 --- a/Package.resolved +++ /dev/null @@ -1,160 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Console", - "repositoryURL": "https://github.com/vapor/console.git", - "state": { - "branch": null, - "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", - "version": "3.1.1" - } - }, - { - "package": "Core", - "repositoryURL": "https://github.com/vapor/core.git", - "state": { - "branch": null, - "revision": "2731f8ba0cf274a61c9bd6ab43550f692ffaf879", - "version": "3.9.0" - } - }, - { - "package": "Crypto", - "repositoryURL": "https://github.com/vapor/crypto.git", - "state": { - "branch": null, - "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", - "version": "3.3.3" - } - }, - { - "package": "DatabaseKit", - "repositoryURL": "https://github.com/vapor/database-kit.git", - "state": { - "branch": null, - "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", - "version": "1.3.3" - } - }, - { - "package": "HTTP", - "repositoryURL": "https://github.com/vapor/http.git", - "state": { - "branch": null, - "revision": "254a0a0cbf22a02b697a075a0d2ddbb448bb7c87", - "version": "3.2.0" - } - }, - { - "package": "Multipart", - "repositoryURL": "https://github.com/vapor/multipart.git", - "state": { - "branch": null, - "revision": "f919a01c4d10a281d6236a21b0b1d1759a72b8eb", - "version": "3.0.4" - } - }, - { - "package": "Routing", - "repositoryURL": "https://github.com/vapor/routing.git", - "state": { - "branch": null, - "revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c", - "version": "3.0.2" - } - }, - { - "package": "Service", - "repositoryURL": "https://github.com/vapor/service.git", - "state": { - "branch": null, - "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", - "version": "1.0.2" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", - "version": "1.14.1" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", - "version": "1.4.0" - } - }, - { - "package": "swift-nio-ssl-support", - "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", - "state": { - "branch": null, - "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", - "version": "1.0.0" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "TemplateKit", - "repositoryURL": "https://github.com/vapor/template-kit.git", - "state": { - "branch": null, - "revision": "121ae51433df94cf6e15c09e1f1b0f7c77ff8d5c", - "version": "1.2.0" - } - }, - { - "package": "URLEncodedForm", - "repositoryURL": "https://github.com/vapor/url-encoded-form.git", - "state": { - "branch": null, - "revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed", - "version": "1.0.6" - } - }, - { - "package": "Validation", - "repositoryURL": "https://github.com/vapor/validation.git", - "state": { - "branch": null, - "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", - "version": "2.1.1" - } - }, - { - "package": "Vapor", - "repositoryURL": "https://github.com/vapor/vapor.git", - "state": { - "branch": null, - "revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6", - "version": "3.3.0" - } - }, - { - "package": "WebSocket", - "repositoryURL": "https://github.com/vapor/websocket.git", - "state": { - "branch": null, - "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", - "version": "1.1.2" - } - } - ] - }, - "version": 1 -} diff --git a/Package.swift b/Package.swift index 15447ab..14a86b4 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,6 @@ import PackageDescription let package = Package( name: "wkhtmltopdf", products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( name: "wkhtmltopdf", targets: ["wkhtmltopdf"]), @@ -14,8 +13,6 @@ let package = Package( .package(url: "https://github.com/vapor/service.git", from: "1.0.0") ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "wkhtmltopdf", dependencies: [ diff --git a/Sources/wkhtmltopdf/Document+Generate.swift b/Sources/wkhtmltopdf/Document+Generate.swift index c266e2e..bdea7d6 100644 --- a/Sources/wkhtmltopdf/Document+Generate.swift +++ b/Sources/wkhtmltopdf/Document+Generate.swift @@ -1,10 +1,6 @@ import Foundation import Service -#if os(Linux) && !swift(>=3.1) -typealias Process = Task -#endif - extension Document { public func generatePDF(on container: Container) throws -> Future { @@ -17,7 +13,7 @@ extension Document { try fileManager.createDirectory(atPath: workDir, withIntermediateDirectories: true) // Save input pages to temp files, and build up args to wkhtmltopdf var wkArgs: [String] = [ - "--zoom", Document.zoom, + "--zoom", self.zoom, "--quiet", "-s", self.paperSize, "-T", "\(self.topMargin)mm", @@ -29,7 +25,7 @@ extension Document { let pageFiles: [String] = try self.pages.map { page in let name = UUID().uuidString + ".html" let filename = "\(workDir)/\(name)" - try File(data: page.content, filename: filename).data.write(to: URL(fileURLWithPath: filename)) + try page.content.write(to: URL(fileURLWithPath: filename)) return filename } defer { diff --git a/Sources/wkhtmltopdf/Document.swift b/Sources/wkhtmltopdf/Document.swift index bbb90c1..af4c7b3 100644 --- a/Sources/wkhtmltopdf/Document.swift +++ b/Sources/wkhtmltopdf/Document.swift @@ -1,8 +1,6 @@ public class Document { - // This may need changing across different platforms and deployments. - static var zoom: String = "1.3" - + let zoom: String let topMargin: Int let rightMargin: Int let bottomMargin: Int @@ -12,7 +10,8 @@ public class Document { public var pages: [Page] = [] - public init(size: String = "A4", margins all: Int? = nil, top: Int? = nil, right: Int? = nil, bottom: Int? = nil, left: Int? = nil) { + public init(size: String = "A4", zoom: String? = nil, margins all: Int? = nil, top: Int? = nil, right: Int? = nil, bottom: Int? = nil, left: Int? = nil) { + self.zoom = zoom ?? "1.3" paperSize = size topMargin = all ?? top ?? 20 rightMargin = all ?? right ?? 20 From 68ce07f0b99af829fcf947b49eff4dc6b8c3d988 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 10 May 2019 07:46:55 +0200 Subject: [PATCH 7/8] Updated README --- README.md | 61 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index de5be57..da8e8ba 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,19 @@ # wkhtmltopdf -![Swift](http://img.shields.io/badge/swift-3.1-brightgreen.svg) -![Vapor](http://img.shields.io/badge/vapor-2.0-brightgreen.svg) +![Swift](http://img.shields.io/badge/swift-4.2-brightgreen.svg) +![Vapor](http://img.shields.io/badge/vapor-3.0-brightgreen.svg) ![Travis](https://travis-ci.org/vapor-community/wkhtmltopdf.svg?branch=master) -Vapor 2 library for converting HTML (Leaf or otherwise) into PDF files using +Vapor 3 library for converting HTML (Leaf or otherwise) into PDF files using [wkhtmltopdf](http://wkhtmltopdf.org/). +## Getting Started + +Add the following in your `Package.swift` file +```Swift +.package(url: "https://github.com/vapor-community/wkhtmltopdf.git", from: "2.0.0"), +``` + ## 📘 Overview First, install [wkhtmltopdf](http://wkhtmltopdf.org/downloads.html). This @@ -15,31 +22,37 @@ library is tested on version 0.12.4. Your binary should be installed at installed correctly. To create a PDF, create and configure a `Document`, add one or more `Page`s, -and then call `generatePDF()`. Here is a full example: +and then call `generatePDF(on: Request)`. Here is a full example: ```Swift import wkhtmltopdf -// Create document. Margins in mm, can be set individually or all at once. -// If no margins are set, the default is 20mm. -let document = Document(margins: 15) -// Create a page from an HTML string. -let page1 = Page("

Page from direct HTML

") -// Create a page from a Leaf template. -let page2 = Page(drop, view: "page_from_leaf_template") -// Create a page from a Leaf template with Context variables. -let page3 = Page(drop, view: "page_from_leaf_template", [ - "firstName": "Peter", - "lastName": "Pan" -]) -// Add the pages to the document -document.pages = [page1, page2, page3] -// Render to a PDF -let pdf = try document.generatePDF() -// Now you can return the PDF as a response, if you want -let response = Response(status: .ok, body: .data(pdf)) -response.headers["Content-Type"] = "application/pdf" -return response +func pdf(_ req: Request) -> Future { + // Create document. Margins in mm, can be set individually or all at once. + // If no margins are set, the default is 20mm. + let document = Document(margins: 15) + // Create a page from an HTML string. + let page1 = Page("

Page from direct HTML

") + // Create a page from a Leaf template. + let page2 = Page(drop, view: "page_from_leaf_template") + // Create a page from a Leaf template with Context variables. + let page3 = Page(drop, view: "page_from_leaf_template", [ + "firstName": "Peter", + "lastName": "Pan" + ]) + // Add the pages to the document + document.pages = [page1, page2, page3] + // Render to a PDF + let pdf = try document.generatePDF(on: req) + // Now you can return the PDF as a response, if you want + return pdf.map { data -> Response in + let http = HTTPResponse(status: HTTPResponseStatus.ok, + headers: HTTPHeaders([("Content-Type", "application/pdf")]), + body: data) + return Response(http: http, + using: req) + } +} ``` In your Leaf file, you may want to load resources such as images, CSS From e9768b3c23889b9210029d6b973a72a2afa49132 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 10 May 2019 08:01:32 +0200 Subject: [PATCH 8/8] =?UTF-8?q?Update=20readme=20to=20use=20Vapor=203=20?= =?UTF-8?q?=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index da8e8ba..2dc842f 100644 --- a/README.md +++ b/README.md @@ -28,29 +28,36 @@ and then call `generatePDF(on: Request)`. Here is a full example: import wkhtmltopdf func pdf(_ req: Request) -> Future { - // Create document. Margins in mm, can be set individually or all at once. + // Create document. Margins in mm, can be set individually or all at once. // If no margins are set, the default is 20mm. let document = Document(margins: 15) // Create a page from an HTML string. let page1 = Page("

Page from direct HTML

") + // Create a page from a Leaf template. - let page2 = Page(drop, view: "page_from_leaf_template") + let page2 = try req.view().render("page_from_leaf_template") + // Create a page from a Leaf template with Context variables. - let page3 = Page(drop, view: "page_from_leaf_template", [ - "firstName": "Peter", - "lastName": "Pan" - ]) - // Add the pages to the document - document.pages = [page1, page2, page3] - // Render to a PDF - let pdf = try document.generatePDF(on: req) - // Now you can return the PDF as a response, if you want - return pdf.map { data -> Response in - let http = HTTPResponse(status: HTTPResponseStatus.ok, - headers: HTTPHeaders([("Content-Type", "application/pdf")]), - body: data) - return Response(http: http, - using: req) + let page3 = try req.view().render("page_from_leaf_template", [ "firstName": "Peter", + "lastName": "Pan"]) + let pages = [ page2, page3].flatten(on: req) + .map { views in + return views.map { Page($0.data) } + } + + return pages.flatMap { pages in + // Add the pages to the document + document.pages = [page1] + pages + // Render to a PDF + let pdf = try document.generatePDF(on: req) + // Now you can return the PDF as a response, if you want + return pdf.map { data -> Response in + let http = HTTPResponse(status: .ok, + headers: HTTPHeaders([("Content-Type", "application/pdf")]), + body: data) + return Response(http: http, + using: req) + } } } ```