@capgo/capacitor-pdf-generator 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorPdfGenerator.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +112 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/android/print/CapgoPdfPrintUtils.java +210 -0
- package/android/src/main/java/app/capgo/pdfgenerator/PdfGeneratorPlugin.java +381 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/xml/capgo_pdf_generator_paths.xml +6 -0
- package/dist/docs.json +109 -0
- package/dist/esm/definitions.d.ts +56 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +24 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/PdfGeneratorPlugin/PdfGeneratorPlugin.swift +292 -0
- package/ios/Tests/PdfGeneratorPluginTests/PdfGeneratorPluginTests.swift +7 -0
- package/package.json +86 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
@objc(PdfGeneratorPlugin)
|
|
6
|
+
public class PdfGeneratorPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
+
public let identifier = "PdfGeneratorPlugin"
|
|
8
|
+
public let jsName = "PdfGenerator"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "fromURL", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "fromData", returnType: CAPPluginReturnPromise),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
private var tasks: [PdfGenerationTask] = []
|
|
15
|
+
|
|
16
|
+
@objc func fromURL(_ call: CAPPluginCall) {
|
|
17
|
+
guard let urlString = call.getString("url"), let url = URL(string: urlString) else {
|
|
18
|
+
call.reject("A valid 'url' is required.")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let options = PdfGeneratorOptions(from: call)
|
|
23
|
+
let task = PdfGenerationTask(plugin: self, call: call, source: .url(url), options: options)
|
|
24
|
+
enqueue(task)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@objc func fromData(_ call: CAPPluginCall) {
|
|
28
|
+
guard let htmlData = call.getString("data") else {
|
|
29
|
+
call.reject("The 'data' option is required.")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let options = PdfGeneratorOptions(from: call)
|
|
34
|
+
let task = PdfGenerationTask(plugin: self, call: call, source: .html(htmlData, options.baseUrl), options: options)
|
|
35
|
+
enqueue(task)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private func enqueue(_ task: PdfGenerationTask) {
|
|
39
|
+
tasks.append(task)
|
|
40
|
+
task.start()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fileprivate func taskDidComplete(_ task: PdfGenerationTask) {
|
|
44
|
+
tasks.removeAll { $0 === task }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fileprivate func handle(pdfData: Data, for task: PdfGenerationTask) {
|
|
48
|
+
switch task.options.outputType {
|
|
49
|
+
case .base64:
|
|
50
|
+
resolveBase64(data: pdfData, for: task)
|
|
51
|
+
case .share:
|
|
52
|
+
share(pdfData: pdfData, for: task)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private func resolveBase64(data: Data, for task: PdfGenerationTask) {
|
|
57
|
+
let base64 = data.base64EncodedString()
|
|
58
|
+
DispatchQueue.main.async {
|
|
59
|
+
task.call.resolve([
|
|
60
|
+
"type": "base64",
|
|
61
|
+
"base64": base64,
|
|
62
|
+
])
|
|
63
|
+
task.finish()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func share(pdfData: Data, for task: PdfGenerationTask) {
|
|
68
|
+
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(task.options.fileName)
|
|
69
|
+
|
|
70
|
+
do {
|
|
71
|
+
try pdfData.write(to: temporaryURL, options: .atomic)
|
|
72
|
+
} catch {
|
|
73
|
+
task.call.reject("Failed to write PDF data: \(error.localizedDescription)")
|
|
74
|
+
task.finish()
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
DispatchQueue.main.async { [weak self] in
|
|
79
|
+
guard let self = self, let presenter = self.bridge?.viewController else {
|
|
80
|
+
task.call.reject("Unable to present share sheet.")
|
|
81
|
+
try? FileManager.default.removeItem(at: temporaryURL)
|
|
82
|
+
task.finish()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let activity = UIActivityViewController(activityItems: [temporaryURL], applicationActivities: nil)
|
|
87
|
+
activity.completionWithItemsHandler = { _, completed, _, error in
|
|
88
|
+
if let error = error {
|
|
89
|
+
task.call.reject("Share failed: \(error.localizedDescription)")
|
|
90
|
+
} else {
|
|
91
|
+
task.call.resolve([
|
|
92
|
+
"type": "share",
|
|
93
|
+
"completed": completed,
|
|
94
|
+
])
|
|
95
|
+
}
|
|
96
|
+
try? FileManager.default.removeItem(at: temporaryURL)
|
|
97
|
+
task.finish()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if let popover = activity.popoverPresentationController, let view = presenter.view {
|
|
101
|
+
popover.sourceView = view
|
|
102
|
+
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
103
|
+
popover.permittedArrowDirections = []
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
presenter.present(activity, animated: true)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private final class PdfGenerationTask: NSObject, WKNavigationDelegate {
|
|
112
|
+
enum Source {
|
|
113
|
+
case url(URL)
|
|
114
|
+
case html(String, URL?)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let call: CAPPluginCall
|
|
118
|
+
let options: PdfGeneratorOptions
|
|
119
|
+
|
|
120
|
+
private weak var plugin: PdfGeneratorPlugin?
|
|
121
|
+
private let source: Source
|
|
122
|
+
private let webView: WKWebView
|
|
123
|
+
private var didFinish = false
|
|
124
|
+
|
|
125
|
+
init(plugin: PdfGeneratorPlugin, call: CAPPluginCall, source: Source, options: PdfGeneratorOptions) {
|
|
126
|
+
self.plugin = plugin
|
|
127
|
+
self.call = call
|
|
128
|
+
self.source = source
|
|
129
|
+
self.options = options
|
|
130
|
+
let configuration = WKWebViewConfiguration()
|
|
131
|
+
configuration.preferences.javaScriptEnabled = true
|
|
132
|
+
self.webView = WKWebView(frame: .zero, configuration: configuration)
|
|
133
|
+
super.init()
|
|
134
|
+
self.webView.navigationDelegate = self
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func start() {
|
|
138
|
+
DispatchQueue.main.async {
|
|
139
|
+
switch self.source {
|
|
140
|
+
case let .url(url):
|
|
141
|
+
let request = URLRequest(url: url)
|
|
142
|
+
self.webView.load(request)
|
|
143
|
+
case let .html(html, baseUrl):
|
|
144
|
+
self.webView.loadHTMLString(html, baseURL: baseUrl)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func finish() {
|
|
150
|
+
guard !didFinish else { return }
|
|
151
|
+
didFinish = true
|
|
152
|
+
cleanup()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private func cleanup() {
|
|
156
|
+
DispatchQueue.main.async {
|
|
157
|
+
self.webView.stopLoading()
|
|
158
|
+
self.webView.navigationDelegate = nil
|
|
159
|
+
self.webView.removeFromSuperview()
|
|
160
|
+
}
|
|
161
|
+
plugin?.taskDidComplete(self)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private func fail(with message: String) {
|
|
165
|
+
guard !didFinish else { return }
|
|
166
|
+
didFinish = true
|
|
167
|
+
DispatchQueue.main.async {
|
|
168
|
+
self.call.reject(message)
|
|
169
|
+
}
|
|
170
|
+
cleanup()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
174
|
+
generatePdf()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
178
|
+
fail(with: "Failed to load content: \(error.localizedDescription)")
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
182
|
+
fail(with: "Failed to load content: \(error.localizedDescription)")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func generatePdf() {
|
|
186
|
+
let configuration = WKPDFConfiguration()
|
|
187
|
+
configuration.rect = CGRect(origin: .zero, size: options.pageSize)
|
|
188
|
+
|
|
189
|
+
webView.createPDF(configuration: configuration) { [weak self] result in
|
|
190
|
+
guard let self else { return }
|
|
191
|
+
switch result {
|
|
192
|
+
case let .success(data):
|
|
193
|
+
self.plugin?.handle(pdfData: data, for: self)
|
|
194
|
+
case let .failure(error):
|
|
195
|
+
self.fail(with: "Failed to generate PDF: \(error.localizedDescription)")
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private struct PdfGeneratorOptions {
|
|
202
|
+
enum OutputType {
|
|
203
|
+
case base64
|
|
204
|
+
case share
|
|
205
|
+
|
|
206
|
+
init(string: String?) {
|
|
207
|
+
switch string?.lowercased() {
|
|
208
|
+
case "share":
|
|
209
|
+
self = .share
|
|
210
|
+
default:
|
|
211
|
+
self = .base64
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
enum DocumentSize {
|
|
217
|
+
case a3
|
|
218
|
+
case a4
|
|
219
|
+
|
|
220
|
+
init(string: String?) {
|
|
221
|
+
switch string?.uppercased() {
|
|
222
|
+
case "A3":
|
|
223
|
+
self = .a3
|
|
224
|
+
default:
|
|
225
|
+
self = .a4
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private var portraitSize: CGSize {
|
|
230
|
+
let pointsPerMillimetre = 72.0 / 25.4
|
|
231
|
+
switch self {
|
|
232
|
+
case .a3:
|
|
233
|
+
return CGSize(width: 297.0 * pointsPerMillimetre, height: 420.0 * pointsPerMillimetre)
|
|
234
|
+
case .a4:
|
|
235
|
+
return CGSize(width: 210.0 * pointsPerMillimetre, height: 297.0 * pointsPerMillimetre)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func size(isLandscape: Bool) -> CGSize {
|
|
240
|
+
let size = portraitSize
|
|
241
|
+
return isLandscape ? CGSize(width: size.height, height: size.width) : size
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let documentSize: DocumentSize
|
|
246
|
+
let isLandscape: Bool
|
|
247
|
+
let outputType: OutputType
|
|
248
|
+
let fileName: String
|
|
249
|
+
let baseUrl: URL?
|
|
250
|
+
|
|
251
|
+
var pageSize: CGSize {
|
|
252
|
+
documentSize.size(isLandscape: isLandscape)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
init(from call: CAPPluginCall) {
|
|
256
|
+
let orientationValue = PdfGeneratorOptions.orientationString(from: call)
|
|
257
|
+
self.documentSize = DocumentSize(string: call.getString("documentSize"))
|
|
258
|
+
self.isLandscape = orientationValue == "landscape"
|
|
259
|
+
self.outputType = OutputType(string: call.getString("type"))
|
|
260
|
+
self.fileName = PdfGeneratorOptions.sanitizedFileName(from: call.getString("fileName"))
|
|
261
|
+
self.baseUrl = PdfGeneratorOptions.baseUrl(from: call.getString("baseUrl"))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private static func orientationString(from call: CAPPluginCall) -> String {
|
|
265
|
+
if let landscapeFlag = call.options["landscape"] as? Bool {
|
|
266
|
+
return landscapeFlag ? "landscape" : "portrait"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let orientation = call.getString("orientation") ?? call.getString("landscape") ?? "portrait"
|
|
270
|
+
return orientation.lowercased()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private static func sanitizedFileName(from raw: String?) -> String {
|
|
274
|
+
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
275
|
+
var name = trimmed.isEmpty ? "default.pdf" : trimmed
|
|
276
|
+
name = name.replacingOccurrences(of: "[/\\\\:]", with: "_", options: .regularExpression)
|
|
277
|
+
if !name.lowercased().hasSuffix(".pdf") {
|
|
278
|
+
name += ".pdf"
|
|
279
|
+
}
|
|
280
|
+
return name
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private static func baseUrl(from raw: String?) -> URL? {
|
|
284
|
+
guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {
|
|
285
|
+
return nil
|
|
286
|
+
}
|
|
287
|
+
if raw == "BUNDLE" {
|
|
288
|
+
return Bundle.main.bundleURL
|
|
289
|
+
}
|
|
290
|
+
return URL(string: raw)
|
|
291
|
+
}
|
|
292
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-pdf-generator",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Generate PDF files from HTML strings or URLs on iOS and Android.",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorPdfGenerator.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-pdf-generator.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/pdf-generator/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"pdf",
|
|
31
|
+
"html",
|
|
32
|
+
"generator"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
36
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorPdfGenerator -destination generic/platform=iOS",
|
|
37
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
38
|
+
"verify:web": "npm run build",
|
|
39
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
40
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --autocorrect --format",
|
|
41
|
+
"eslint": "eslint . --ext .ts",
|
|
42
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java,kt,swift}\" --plugin=prettier-plugin-java",
|
|
43
|
+
"swiftlint": "node-swiftlint",
|
|
44
|
+
"docgen": "docgen --api PdfGeneratorPlugin --output-readme README.md --output-json dist/docs.json",
|
|
45
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
46
|
+
"clean": "rimraf ./dist",
|
|
47
|
+
"watch": "tsc --watch",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@capacitor/android": "^7.0.0",
|
|
52
|
+
"@capacitor/cli": "^7.0.0",
|
|
53
|
+
"@capacitor/core": "^7.0.0",
|
|
54
|
+
"@capacitor/docgen": "^0.3.0",
|
|
55
|
+
"@capacitor/ios": "^7.0.0",
|
|
56
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
57
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
58
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
59
|
+
"@types/node": "^22.13.1",
|
|
60
|
+
"eslint": "^8.57.0",
|
|
61
|
+
"eslint-plugin-import": "^2.31.0",
|
|
62
|
+
"husky": "^9.1.7",
|
|
63
|
+
"prettier": "^3.4.2",
|
|
64
|
+
"prettier-plugin-java": "^2.6.7",
|
|
65
|
+
"rimraf": "^6.0.1",
|
|
66
|
+
"rollup": "^4.34.6",
|
|
67
|
+
"swiftlint": "^2.0.0",
|
|
68
|
+
"typescript": "^5.7.3"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@capacitor/core": ">=7.0.0"
|
|
72
|
+
},
|
|
73
|
+
"eslintConfig": {
|
|
74
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
75
|
+
},
|
|
76
|
+
"prettier": "@ionic/prettier-config",
|
|
77
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
78
|
+
"capacitor": {
|
|
79
|
+
"ios": {
|
|
80
|
+
"src": "ios"
|
|
81
|
+
},
|
|
82
|
+
"android": {
|
|
83
|
+
"src": "android"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|