Skip to content

Commit 225e478

Browse files
Merge pull request #22 from AlessioLuciani/dev
v0.4.0
2 parents 3aea262 + 78011e6 commit 225e478

File tree

23 files changed

+852
-280
lines changed

23 files changed

+852
-280
lines changed

.github/workflows/flutter.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Flutter CI
2+
3+
on:
4+
push:
5+
branches: [ dev, master ]
6+
pull_request:
7+
branches: [ dev, master ]
8+
9+
jobs:
10+
build:
11+
12+
runs-on: macos-latest #ubuntu-latest
13+
env:
14+
working-directory: ./example
15+
16+
steps:
17+
- uses: actions/checkout@v2
18+
- uses: actions/setup-java@v1
19+
with:
20+
java-version: '12.x'
21+
- uses: subosito/flutter-action@v1
22+
with:
23+
channel: 'beta' # or: 'dev' or 'stable'
24+
- run: flutter pub get
25+
working-directory: ${{env.working-directory}}
26+
# - run: flutter test
27+
- run: flutter build appbundle #apk
28+
working-directory: ${{env.working-directory}}
29+
- run: flutter build ios --release --no-codesign
30+
working-directory: ${{env.working-directory}}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.4.0
2+
3+
* The PDFBoxResourceLoader is now used on Android to load PDF documents much faster than before. The fast initialization (i.e. *fastInit*) option has therefore been removed.
4+
* PDF documents are no longer kept alive in the platform-specific scope. Instead, they are opened and closed at each read with the respective library functions. This does not affect the caching mechanism utilized directly in Dart. This change prevents errors due to multiple document accesses at the same time.
5+
* Tests have been implemented.
6+
17
## 0.3.1
28

39
* The possibility to initialize a document faster (without immediately initializing the text stripper engine) on Android has been added.

README.md

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# PDF Text Plugin
22

33
[![Pub Version](https://img.shields.io/pub/v/pdf_text)](https://pub.dev/packages/pdf_text)
4-
[![GitHub issues](https://img.shields.io/github/issues/AlessioLuciani/flutter-pdf-text)](https://github.com/AlessioLuciani/flutter-pdf-text/issues)
4+
![Flutter CI](https://github.com/AlessioLuciani/flutter-pdf-text/workflows/Flutter%20CI/badge.svg?branch=master)
55
[![GitHub forks](https://img.shields.io/github/forks/AlessioLuciani/flutter-pdf-text)](https://github.com/AlessioLuciani/flutter-pdf-text/network)
66
[![GitHub stars](https://img.shields.io/github/stars/AlessioLuciani/flutter-pdf-text)](https://github.com/AlessioLuciani/flutter-pdf-text/stargazers)
77
[![GitHub license](https://img.shields.io/github/license/AlessioLuciani/flutter-pdf-text)](https://github.com/AlessioLuciani/flutter-pdf-text/blob/master/LICENSE)
@@ -18,7 +18,7 @@ Add this to your package's `pubspec.yaml` file:
1818

1919
```yaml
2020
dependencies:
21-
pdf_text: ^0.3.1
21+
pdf_text: ^0.4.0
2222
```
2323
2424
## Usage
@@ -29,7 +29,7 @@ Import the package with:
2929
import 'package:pdf_text/pdf_text.dart';
3030
```
3131

32-
*Create a PDF document instance using a File object:*
32+
**Create a PDF document instance using a File object:**
3333

3434
```dart
3535
PDFDoc doc = await PDFDoc.fromFile(file);
@@ -53,13 +53,7 @@ Pass a password for encrypted PDF documents:
5353
PDFDoc doc = await PDFDoc.fromFile(file, password: password);
5454
```
5555

56-
Use faster initialization on Android:
57-
58-
```dart
59-
PDFDoc doc = await PDFDoc.fromFile(file, fastInit: true);
60-
```
61-
62-
*Read the text of the entire document:*
56+
**Read the text of the entire document:**
6357

6458
```dart
6559
String docText = await doc.text;
@@ -71,19 +65,19 @@ Retrieve the number of pages of the document:
7165
int numPages = doc.length;
7266
```
7367

74-
*Access a page of the document:*
68+
**Access a page of the document:**
7569

7670
```dart
7771
PDFPage page = doc.pageAt(pageNumber);
7872
```
7973

80-
*Read the text of a page of the document:*
74+
**Read the text of a page of the document:**
8175

8276
```dart
8377
String pageText = await page.text;
8478
```
8579

86-
*Read the information of the document:*
80+
**Read the information of the document:**
8781

8882
```dart
8983
PDFDocInfo info = doc.info;
@@ -119,9 +113,9 @@ allows you not to waste time loading text that you will probably not use. When y
119113
| Return | Description |
120114
|---|---|
121115
| PDFPage | **pageAt(int pageNumber)** <br> Gets the page of the document at the given page number. |
122-
| static Future\<PDFDoc> | **fromFile(File file, {String password = "", bool fastInit = false})** <br> Creates a PDFDoc object with a File instance. Optionally, takes a password for encrypted PDF documents. If fastInit is true, the initialization of the document will be faster on Android. In that case, the text stripper engine will not be initialized with this call, but later when some text is read. This means that the first text read will take some time but the document data can be accessed immediately.|
123-
| static Future\<PDFDoc> | **fromPath(String path, {String password = "", bool fastInit = false})** <br> Creates a PDFDoc object with a file path. Optionally, takes a password for encrypted PDF documents. If fastInit is true, the initialization of the document will be faster on Android. In that case, the text stripper engine will not be initialized with this call, but later when some text is read. This means that the first text read will take some time but the document data can be accessed immediately.|
124-
| static Future\<PDFDoc> | **fromURL(String url, {String password = "", bool fastInit = false})** <br> Creates a PDFDoc object with a url. Optionally, takes a password for encrypted PDF documents. If fastInit is true, the initialization of the document will be faster on Android. In that case, the text stripper engine will not be initialized with this call, but later when some text is read. This means that the first text read will take some time but the document data can be accessed immediately. It downloads the PDF file located in the given URL and saves it in the app's temporary directory. |
116+
| static Future\<PDFDoc> | **fromFile(File file, {String password = ""})** <br> Creates a PDFDoc object with a File instance. Optionally, takes a password for encrypted PDF documents.|
117+
| static Future\<PDFDoc> | **fromPath(String path, {String password = ""})** <br> Creates a PDFDoc object with a file path. Optionally, takes a password for encrypted PDF documents.|
118+
| static Future\<PDFDoc> | **fromURL(String url, {String password = ""})** <br> Creates a PDFDoc object with a url. Optionally, takes a password for encrypted PDF documents.|
125119
| void | **deleteFile()** <br> Deletes the file related to this PDFDoc.<br>Throws an exception if the FileSystemEntity cannot be deleted. |
126120
| static Future | **deleteAllExternalFiles()** <br> Deletes all the files of the documents that have been imported from outside the local file system (e.g. using fromURL). |
127121

@@ -155,4 +149,4 @@ class PDFDocInfo {
155149
## Contribute
156150

157151
If you have any suggestions, improvements or issues, feel free to contribute to this project.
158-
You can either submit a new issue or propose a pull request.
152+
You can either submit a new issue or propose a pull request. Direct your pull requests into the *dev* branch.
Lines changed: 71 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
11
package dev.aluc.pdf_text
22

3+
34
import android.os.Handler
45
import android.os.Looper
56
import androidx.annotation.NonNull
7+
import com.tom_roush.pdfbox.pdmodel.PDDocument
8+
import com.tom_roush.pdfbox.text.PDFTextStripper
9+
import com.tom_roush.pdfbox.util.PDFBoxResourceLoader
610
import io.flutter.embedding.engine.plugins.FlutterPlugin
711
import io.flutter.plugin.common.MethodCall
812
import io.flutter.plugin.common.MethodChannel
913
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
1014
import io.flutter.plugin.common.MethodChannel.Result
1115
import io.flutter.plugin.common.PluginRegistry.Registrar
12-
1316
import java.io.File
14-
15-
16-
import com.tom_roush.pdfbox.pdmodel.PDDocument
17-
import com.tom_roush.pdfbox.pdmodel.PDPage
18-
import com.tom_roush.pdfbox.text.PDFTextStripper
1917
import kotlin.concurrent.thread
2018

2119
/** PdfTextPlugin */
22-
public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
23-
24-
/**
25-
* PDF document cached from the previous use.
26-
*/
27-
private var cachedDoc: PDDocument? = null
28-
private var cachedDocPath: String? = null
29-
30-
/**
31-
* PDF text stripper.
32-
*/
33-
private var pdfTextStripper = PDFTextStripper()
20+
class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
3421

3522
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
36-
val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "pdf_text")
37-
channel.setMethodCallHandler(PdfTextPlugin());
23+
val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "pdf_text")
24+
channel.setMethodCallHandler(PdfTextPlugin())
25+
PDFBoxResourceLoader.init(flutterPluginBinding.applicationContext)
3826
}
3927

40-
41-
4228
// This static function is optional and equivalent to onAttachedToEngine. It supports the old
4329
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
4430
// plugin registration via this function while apps migrate to use the new Android APIs
@@ -53,36 +39,38 @@ public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
5339
fun registerWith(registrar: Registrar) {
5440
val channel = MethodChannel(registrar.messenger(), "pdf_text")
5541
channel.setMethodCallHandler(PdfTextPlugin())
42+
PDFBoxResourceLoader.init(registrar.context())
5643
}
5744
}
5845

5946
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
6047
thread (start = true) {
6148
when (call.method) {
6249
"initDoc" -> {
63-
val args = call.arguments as Map<String, Any>
50+
val args = call.arguments as Map<*, *>
6451
val path = args["path"] as String
6552
val password = args["password"] as String
66-
val fastInit = args["fastInit"] as Boolean
67-
initDoc(result, path, password, fastInit)
53+
initDoc(result, path, password)
6854
}
6955
"getDocPageText" -> {
70-
val args = call.arguments as Map<String, Any>
56+
val args = call.arguments as Map<*, *>
7157
val path = args["path"] as String
7258
val pageNumber = args["number"] as Int
73-
getDocPageText(result, path, pageNumber)
59+
val password = args["password"] as String
60+
getDocPageText(result, path, pageNumber, password)
7461
}
7562
"getDocText" -> {
76-
val args = call.arguments as Map<String, Any>
63+
val args = call.arguments as Map<*, *>
7764
val path = args["path"] as String
65+
@Suppress("UNCHECKED_CAST")
7866
val missingPagesNumbers = args["missingPagesNumbers"] as List<Int>
79-
getDocText(result, path, missingPagesNumbers)
67+
val password = args["password"] as String
68+
getDocText(result, path, missingPagesNumbers, password)
8069
}
8170
else -> {
8271
Handler(Looper.getMainLooper()).post {
8372
result.notImplemented()
8473
}
85-
8674
}
8775
}
8876
}
@@ -94,34 +82,35 @@ public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
9482
/**
9583
Initializes the PDF document and returns some information into the channel.
9684
*/
97-
private fun initDoc(result: Result, path: String, password: String, fastInit: Boolean) {
98-
val doc = getDoc(result, path, password, !fastInit) ?: return
99-
// Getting the length of the PDF document in pages.
100-
val length = doc.numberOfPages
85+
private fun initDoc(result: Result, path: String, password: String) {
86+
getDoc(result, path, password)?.use { doc ->
87+
// Getting the length of the PDF document in pages.
88+
val length = doc.numberOfPages
10189

102-
val info = doc.documentInformation
90+
val info = doc.documentInformation
10391

104-
var creationDate: String? = null
105-
if (info.creationDate != null) {
106-
creationDate = info.creationDate.time.toString()
107-
}
108-
var modificationDate: String? = null
109-
if (info.modificationDate != null) {
110-
modificationDate = info.modificationDate.time.toString()
111-
}
112-
val data = hashMapOf<String, Any>(
113-
"length" to length,
114-
"info" to hashMapOf("author" to info.author,
115-
"creationDate" to creationDate,
116-
"modificationDate" to modificationDate,
117-
"creator" to info.creator, "producer" to info.producer,
118-
"keywords" to splitKeywords(info.keywords),
119-
"title" to info.title, "subject" to info.subject
120-
)
121-
)
122-
123-
Handler(Looper.getMainLooper()).post {
124-
result.success(data)
92+
var creationDate: String? = null
93+
if (info.creationDate != null) {
94+
creationDate = info.creationDate.time.toString()
95+
}
96+
var modificationDate: String? = null
97+
if (info.modificationDate != null) {
98+
modificationDate = info.modificationDate.time.toString()
99+
}
100+
val data = hashMapOf<String, Any>(
101+
"length" to length,
102+
"info" to hashMapOf("author" to info.author,
103+
"creationDate" to creationDate,
104+
"modificationDate" to modificationDate,
105+
"creator" to info.creator, "producer" to info.producer,
106+
"keywords" to splitKeywords(info.keywords),
107+
"title" to info.title, "subject" to info.subject
108+
)
109+
)
110+
doc.close()
111+
Handler(Looper.getMainLooper()).post {
112+
result.success(data)
113+
}
125114
}
126115
}
127116

@@ -132,7 +121,7 @@ public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
132121
if (keywordsString == null) {
133122
return null
134123
}
135-
var keywords = keywordsString.split(",").toMutableList()
124+
val keywords = keywordsString.split(",").toMutableList()
136125
for (i in keywords.indices) {
137126
var keyword = keywords[i]
138127
keyword = keyword.dropWhile { it == ' ' }
@@ -145,51 +134,45 @@ public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
145134
/**
146135
Gets the text of a document page, given its number.
147136
*/
148-
private fun getDocPageText(result: Result, path: String, pageNumber: Int) {
149-
val doc = getDoc(result, path) ?: return
150-
pdfTextStripper.startPage = pageNumber
151-
pdfTextStripper.endPage = pageNumber
152-
val text = pdfTextStripper.getText(doc)
153-
Handler(Looper.getMainLooper()).post {
154-
result.success(text)
137+
private fun getDocPageText(result: Result, path: String, pageNumber: Int, password: String) {
138+
getDoc(result, path, password)?.use { doc ->
139+
val stripper = PDFTextStripper();
140+
stripper.startPage = pageNumber
141+
stripper.endPage = pageNumber
142+
val text = stripper.getText(doc)
143+
doc.close()
144+
Handler(Looper.getMainLooper()).post {
145+
result.success(text)
146+
}
155147
}
156148
}
157149

158150
/**
159151
Gets the text of the entire document.
160152
In order to improve the performance, it only retrieves the pages that are currently missing.
161153
*/
162-
private fun getDocText(result: Result, path: String, missingPagesNumbers: List<Int>) {
163-
val doc = getDoc(result, path) ?: return
164-
var missingPagesTexts = arrayListOf<String>()
165-
missingPagesNumbers.forEach {
166-
pdfTextStripper.startPage = it
167-
pdfTextStripper.endPage = it
168-
missingPagesTexts.add(pdfTextStripper.getText(doc))
169-
}
170-
Handler(Looper.getMainLooper()).post {
171-
result.success(missingPagesTexts)
154+
private fun getDocText(result: Result, path: String, missingPagesNumbers: List<Int>, password: String) {
155+
getDoc(result, path, password)?.use { doc ->
156+
val missingPagesTexts = arrayListOf<String>()
157+
val stripper = PDFTextStripper();
158+
missingPagesNumbers.forEach {
159+
stripper.startPage = it
160+
stripper.endPage = it
161+
missingPagesTexts.add(stripper.getText(doc))
162+
}
163+
doc.close()
164+
Handler(Looper.getMainLooper()).post {
165+
result.success(missingPagesTexts)
166+
}
172167
}
173168
}
174169

175170
/**
176171
Gets a PDF document, given its path.
177-
Initializes the text stripper engine if initTextStripper is true.
178172
*/
179-
private fun getDoc(result: Result, path: String, password: String = "",
180-
initTextStripper: Boolean = true): PDDocument? {
181-
// Checking for cached document
182-
if (cachedDoc != null && cachedDocPath == path) {
183-
return cachedDoc
184-
}
173+
private fun getDoc(result: Result, path: String, password: String = ""): PDDocument? {
185174
return try {
186-
val doc = PDDocument.load(File(path), password)
187-
cachedDoc = doc
188-
cachedDocPath = path
189-
if (initTextStripper) {
190-
initTextStripperEngine(doc)
191-
}
192-
doc
175+
PDDocument.load(File(path), password)
193176
} catch (e: Exception) {
194177
Handler(Looper.getMainLooper()).post {
195178
result.error("INVALID_PATH",
@@ -199,13 +182,4 @@ public class PdfTextPlugin: FlutterPlugin, MethodCallHandler {
199182
null
200183
}
201184
}
202-
203-
/**
204-
* Initializes the text stripper engine. This can take some time.
205-
*/
206-
private fun initTextStripperEngine(doc: PDDocument) {
207-
pdfTextStripper.startPage = 1
208-
pdfTextStripper.endPage = 1
209-
pdfTextStripper.getText(doc)
210-
}
211185
}

0 commit comments

Comments
 (0)