Skip to content

Commit

Permalink
Loading banner and partial link support.
Browse files Browse the repository at this point in the history
  • Loading branch information
espresso3389 committed Jan 12, 2024
1 parent 286caf0 commit 0ab7bf1
Show file tree
Hide file tree
Showing 12 changed files with 834 additions and 94 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"alloc",
"ANNOT",
"Annots",
"Antialiasing",
"ARGB",
"bgra",
Expand Down Expand Up @@ -43,6 +44,7 @@
"Pdfjs",
"pdfrx",
"pubspec",
"RECTF",
"rects",
"reentrantly",
"relayout",
Expand Down Expand Up @@ -138,4 +140,4 @@
"xloctime": "cpp",
"xstring": "cpp"
}
}
}
31 changes: 30 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,11 @@ class _MyAppState extends State<MyApp> {
// documentSize: Size(x, height),
// );
// },
// Thumbs for vertical/horizontal scroll
//
// Scroll-thumbs example
//
viewerOverlayBuilder: (context, size) => [
// Show vertical scroll thumb on the right; it has page number on it
PdfViewerScrollThumb(
controller: controller,
orientation: ScrollbarOrientation.right,
Expand All @@ -85,6 +88,7 @@ class _MyAppState extends State<MyApp> {
),
),
),
// Just a simple horizontal scroll thumb on the bottom
PdfViewerScrollThumb(
controller: controller,
orientation: ScrollbarOrientation.bottom,
Expand All @@ -96,6 +100,31 @@ class _MyAppState extends State<MyApp> {
),
),
],
//
// Loading progress indicator example
//
loadingBannerBuilder: (context, bytesDownloaded, totalBytes) =>
Center(
child: CircularProgressIndicator(
value: totalBytes != null
? bytesDownloaded / totalBytes
: null,
backgroundColor: Colors.grey,
),
),
//
// Link handling example
//
// FIXME: a link with several areas (link that contains line-break) does not correctly
// show the hover status
// FIXME: gestures other than tap should be passed-through to the underlying widget
linkWidgetBuilder: (context, link, size) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => print('link tapped: ${link.url}'),
hoverColor: Colors.blue.withOpacity(0.2),
),
),
),
),
AnimatedPositioned(
Expand Down
1 change: 1 addition & 0 deletions lib/pdfrx.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export 'src/pdf_api.dart';
export 'src/pdf_document_store.dart';
export 'src/pdf_file_cache.dart';
export 'src/pdf_page_text.dart';
export 'src/pdf_viewer_params.dart';
export 'src/pdf_viewer_scroll_thumb.dart';
export 'src/pdf_widgets.dart';
18 changes: 17 additions & 1 deletion lib/src/pdf_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ abstract class PdfDocumentFactory {
Uri uri, {
String? password,
PdfPasswordProvider? passwordProvider,
PdfDownloadProgressCallback? progressCallback,
});

/// Singleton [PdfDocumentFactory] instance.
Expand All @@ -59,6 +60,15 @@ abstract class PdfDocumentFactory {
static PdfDocumentFactory instance = PdfDocumentFactoryImpl();
}

/// Callback function to notify download progress.
///
/// [downloadedBytes] is the number of bytes downloaded so far.
/// [totalBytes] is the total number of bytes to download. It may be `null` if the total size is unknown.
typedef PdfDownloadProgressCallback = void Function(
int downloadedBytes, [
int? totalBytes,
]);

/// Function to provide password for encrypted PDF.
///
/// The function is called when PDF requires password.
Expand Down Expand Up @@ -163,16 +173,20 @@ abstract class PdfDocument {
///
/// For Flutter Web, the implementation uses browser's function and restricted by CORS.
// ignore: comment_references
/// For other platforms, it uses [pdfDocumentFromUri] that uses HTTP's range request to download the file .
/// For other platforms, it uses [pdfDocumentFromUri] that uses HTTP's range request to download the file.
///
/// [progressCallback] is called when the download progress is updated (Not supported on Web).
static Future<PdfDocument> openUri(
Uri uri, {
String? password,
PdfPasswordProvider? passwordProvider,
PdfDownloadProgressCallback? progressCallback,
}) =>
PdfDocumentFactory.instance.openUri(
uri,
password: password,
passwordProvider: passwordProvider,
progressCallback: progressCallback,
);

/// Pages.
Expand Down Expand Up @@ -240,6 +254,8 @@ abstract class PdfPage {
/// Create Text object to extract text from the page.
/// The returned object should be disposed after use.
Future<PdfPageText> loadText();

Future<List<PdfLink>> loadLinks();
}

/// Annotation rendering mode.
Expand Down
25 changes: 22 additions & 3 deletions lib/src/pdf_document_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class PdfDocumentRef extends Listenable {
PdfDocument? _document;
Object? _error;
int _revision;
int _bytesDownloaded = 0;
int? _totalBytes;

/// The [PdfDocument] instance if available.
PdfDocument? get document => _document;
Expand All @@ -27,6 +29,9 @@ class PdfDocumentRef extends Listenable {

int get revision => _revision;

int get bytesDownloaded => _bytesDownloaded;
int? get totalBytes => _totalBytes;

@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
Expand All @@ -53,15 +58,21 @@ class PdfDocumentRef extends Listenable {
_document = null;
}

void _progress(int progress, [int? total]) {
_bytesDownloaded = progress;
_totalBytes = total;
notifyListeners();
}

Future<bool> setDocument(
FutureOr<PdfDocument> Function() documentLoader, {
PdfDocumentLoaderFunction documentLoader, {
bool resetOnError = false,
}) =>
store.synchronized(
() async {
try {
final oldDocument = _document;
final newDocument = await documentLoader();
final newDocument = await documentLoader(_progress);
if (newDocument == oldDocument) {
return false;
}
Expand All @@ -84,6 +95,12 @@ class PdfDocumentRef extends Listenable {
);
}

/// Function to load a [PdfDocument].
///
/// The load process may call [progressCallback] to report the download/load progress if loader can do that.
typedef PdfDocumentLoaderFunction = Future<PdfDocument> Function(
PdfDownloadProgressCallback progressCallback);

/// A store to maintain [PdfDocumentRef] instances.
///
/// [PdfViewer] instances using the same [PdfDocumentStore] share the same [PdfDocumentRef] instances.
Expand All @@ -103,7 +120,9 @@ class PdfDocumentStore {
/// does nothing and returns existing [PdfDocumentRef] instance that indicates the error.
PdfDocumentRef load(
String sourceName, {
required Future<PdfDocument> Function() documentLoader,
required Future<PdfDocument> Function(
PdfDownloadProgressCallback progressCallback)
documentLoader,
bool retryIfError = false,
}) {
final docRef = _docRefs.putIfAbsent(
Expand Down
27 changes: 18 additions & 9 deletions lib/src/pdf_file_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class PdfFileCacheNative extends PdfFileCache {
final dir1 = fnHash.substring(0, 2);
final dir2 = fnHash.substring(2, 4);
final body = fnHash.substring(4);
final dir = Directory(path.join(cacheDir.path, dir1, dir2));
final dir = Directory(path.join(cacheDir.path, 'pdfrx.cache', dir1, dir2));
await dir.create(recursive: true);
return File(path.join(dir.path, '$body.pdf'));
}
Expand All @@ -278,12 +278,15 @@ Future<PdfDocument> pdfDocumentFromUri(
String? password,
PdfPasswordProvider? passwordProvider,
int? blockSize,
PdfFileCache? cache,
PdfDownloadProgressCallback? progressCallback,
}) async {
final cache = await PdfFileCache.fromUri(uri);
progressCallback?.call(0);
cache ??= await PdfFileCache.fromUri(uri);

if (!cache.isInitialized) {
cache.setBlockSize(blockSize ?? PdfFileCache.defaultBlockSize);
final result = await _downloadBlock(uri, cache, 0);
final result = await _downloadBlock(uri, cache, progressCallback, 0);
if (result.isFullDownload) {
return PdfDocument.openFile(
cache.filePath,
Expand All @@ -297,7 +300,7 @@ Future<PdfDocument> pdfDocumentFromUri(
final eTag = response.headers['etag'];
if (eTag != cache.eTag) {
await cache.resetAll();
final result = await _downloadBlock(uri, cache, 0);
final result = await _downloadBlock(uri, cache, progressCallback, 0);
if (result.isFullDownload) {
return PdfDocument.openFile(
cache.filePath,
Expand All @@ -314,10 +317,10 @@ Future<PdfDocument> pdfDocumentFromUri(
final end = position + size;
int bufferPosition = 0;
for (int p = position; p < end;) {
final blockId = p ~/ cache.blockSize;
final blockId = p ~/ cache!.blockSize;
final isAvailable = cache.isCached(blockId);
if (!isAvailable) {
await _downloadBlock(uri, cache, blockId);
await _downloadBlock(uri, cache, progressCallback, blockId);
}
final readEnd = min(p + size, (blockId + 1) * cache.blockSize);
final sizeToRead = readEnd - p;
Expand All @@ -332,7 +335,7 @@ Future<PdfDocument> pdfDocumentFromUri(
passwordProvider: passwordProvider,
fileSize: cache.fileSize,
sourceName: uri.toString(),
onDispose: () => cache.close(),
onDispose: () => cache!.close(),
);
}

Expand All @@ -343,8 +346,13 @@ class _DownloadResult {
}

// Download blocks of the file and cache the data to file.
Future<_DownloadResult> _downloadBlock(Uri uri, PdfFileCache cache, int blockId,
{int blockCount = 1}) async {
Future<_DownloadResult> _downloadBlock(
Uri uri,
PdfFileCache cache,
PdfDownloadProgressCallback? progressCallback,
int blockId, {
int blockCount = 1,
}) async {
int? fileSize;
final blockOffset = blockId * cache.blockSize;
final end = blockOffset + cache.blockSize * blockCount;
Expand All @@ -370,5 +378,6 @@ Future<_DownloadResult> _downloadBlock(Uri uri, PdfFileCache cache, int blockId,
} else {
await cache.setCached(blockId, lastBlock: blockId + blockCount - 1);
}
progressCallback?.call(cache.cachedBytes, fileSize);
return _DownloadResult(fileSize!, isFullDownload);
}
Loading

0 comments on commit 0ab7bf1

Please sign in to comment.